Merge branch 'master' into feature/tr-host-under-path

Conflicts:
  - CHANGELOG.md
pull/10616/head
Tim Raymond 2017-01-27 19:30:43 -05:00
commit 2102b779f4
23 changed files with 634 additions and 135 deletions

View File

@ -2,13 +2,14 @@
### Upcoming Bug Fixes
1. [#788](https://github.com/influxdata/chronograf/pull/788): Fix missing fields in data explorer when using non-default retention policy
1. [#774](https://github.com/influxdata/chronograf/issues/774): Fix gaps in layouts for hosts
2. [#774](https://github.com/influxdata/chronograf/issues/774): Fix gaps in layouts for hosts
### Upcoming Features
1. [#779](https://github.com/influxdata/chronograf/issues/779): Add layout for telegraf's diskio system plugin
1. [#810](https://github.com/influxdata/chronograf/issues/810): Add layout for telegraf's net system plugin
1. [#811](https://github.com/influxdata/chronograf/issues/811): Add layout for telegraf's procstat plugin
1. [#814](https://github.com/influxdata/chronograf/issues/814): Allows Chronograf to be mounted under any arbitrary URL path using the `--basepath` flag.
2. [#810](https://github.com/influxdata/chronograf/issues/810): Add layout for telegraf's net system plugin
3. [#811](https://github.com/influxdata/chronograf/issues/811): Add layout for telegraf's procstat plugin
4. [#737](https://github.com/influxdata/chronograf/issues/737): Add GUI for OpsGenie kapacitor alert service
5. [#814](https://github.com/influxdata/chronograf/issues/814): Allows Chronograf to be mounted under any arbitrary URL path using the `--basepath` flag.
### Upcoming UI Improvements

View File

@ -58,8 +58,8 @@ After installing gvm you can install and set the default go version by
running the following:
```bash
gvm install go1.7.4
gvm use go1.7.4 --default
gvm install go1.7.5
gvm use go1.7.5 --default
```
Installing GDM

View File

@ -69,10 +69,12 @@ A UI for [Kapacitor](https://github.com/influxdata/kapacitor) alert creation and
* Preview data and alert boundaries while creating an alert
* Configure alert destinations - Currently, Chronograf supports sending alerts to:
* HipChat
* OpsGenie
* PagerDuty
* Sensu
* Slack
* SMTP
* Talk
* Telegram
* VictorOps
* View all active alerts at a glance on the alerting dashboard
@ -113,7 +115,7 @@ docker pull quay.io/influxdb/chronograf:latest
### From Source
* Chronograf works with go 1.7.4, npm 3.10.7 and node v6.6.0. Additional version support of these projects will be implemented soon, but these are the only supported versions to date.
* Chronograf works with go 1.7.x, node 6.x/7.x, and npm 3.x.
* Chronograf requires [Kapacitor](https://github.com/influxdata/kapacitor) 1.1.x to create and store alerts.
1. [Install Go](https://golang.org/doc/install)

View File

@ -194,6 +194,22 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error {
func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
cells := make([]*DashboardCell, len(d.Cells))
for i, c := range d.Cells {
queries := make([]*Query, len(c.Queries))
for j, q := range c.Queries {
r := new(Range)
if q.Range != nil {
r.Upper, r.Lower = q.Range.Upper, q.Range.Lower
}
queries[j] = &Query{
Command: q.Command,
DB: q.DB,
RP: q.RP,
GroupBys: q.GroupBys,
Wheres: q.Wheres,
Label: q.Label,
Range: r,
}
}
cells[i] = &DashboardCell{
X: c.X,
@ -201,7 +217,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
W: c.W,
H: c.H,
Name: c.Name,
Queries: c.Queries,
Queries: queries,
Type: c.Type,
}
}
@ -220,23 +236,39 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
return err
}
cells := make([]chronograf.DashboardCell, len(d.Cells))
for i, c := range d.Cells {
cells := make([]chronograf.DashboardCell, len(pb.Cells))
for i, c := range pb.Cells {
queries := make([]chronograf.Query, len(c.Queries))
for j, q := range c.Queries {
queries[j] = chronograf.Query{
Command: q.Command,
DB: q.DB,
RP: q.RP,
GroupBys: q.GroupBys,
Wheres: q.Wheres,
Label: q.Label,
}
if q.Range.Upper != q.Range.Lower {
queries[j].Range = &chronograf.Range{
Upper: q.Range.Upper,
Lower: q.Range.Lower,
}
}
}
cells[i] = chronograf.DashboardCell{
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: c.Queries,
Queries: queries,
Type: c.Type,
}
}
d.ID = chronograf.DashboardID(pb.ID)
d.Cells = cells
d.Name = pb.Name
return nil
}

View File

@ -39,13 +39,13 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type Exploration struct {
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,json=userID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,json=data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,json=createdAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,json=updatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"`
}
func (m *Exploration) Reset() { *m = Exploration{} }
@ -54,15 +54,15 @@ func (*Exploration) ProtoMessage() {}
func (*Exploration) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
type Source struct {
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
Type string `protobuf:"bytes,3,opt,name=Type,json=type,proto3" json:"Type,omitempty"`
Username string `protobuf:"bytes,4,opt,name=Username,json=username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=Password,json=password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,6,opt,name=URL,json=uRL,proto3" json:"URL,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
Telegraf string `protobuf:"bytes,8,opt,name=Telegraf,json=telegraf,proto3" json:"Telegraf,omitempty"`
InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,json=insecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
Type string `protobuf:"bytes,3,opt,name=Type,proto3" json:"Type,omitempty"`
Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,6,opt,name=URL,proto3" json:"URL,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"`
Telegraf string `protobuf:"bytes,8,opt,name=Telegraf,proto3" json:"Telegraf,omitempty"`
InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"`
}
func (m *Source) Reset() { *m = Source{} }
@ -71,8 +71,8 @@ func (*Source) ProtoMessage() {}
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
type Dashboard struct {
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"`
}
@ -93,7 +93,7 @@ type DashboardCell struct {
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"`
H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"`
Queries []string `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"`
}
@ -103,13 +103,20 @@ func (m *DashboardCell) String() string { return proto.CompactTextStr
func (*DashboardCell) ProtoMessage() {}
func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (m *DashboardCell) GetQueries() []*Query {
if m != nil {
return m.Queries
}
return nil
}
type Server struct {
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
Username string `protobuf:"bytes,3,opt,name=Username,json=username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,4,opt,name=Password,json=password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,5,opt,name=URL,json=uRL,proto3" json:"URL,omitempty"`
SrcID int64 `protobuf:"varint,6,opt,name=SrcID,json=srcID,proto3" json:"SrcID,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
Username string `protobuf:"bytes,3,opt,name=Username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,4,opt,name=Password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,5,opt,name=URL,proto3" json:"URL,omitempty"`
SrcID int64 `protobuf:"varint,6,opt,name=SrcID,proto3" json:"SrcID,omitempty"`
}
func (m *Server) Reset() { *m = Server{} }
@ -118,11 +125,11 @@ func (*Server) ProtoMessage() {}
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
type Layout struct {
ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Application string `protobuf:"bytes,2,opt,name=Application,json=application,proto3" json:"Application,omitempty"`
Measurement string `protobuf:"bytes,3,opt,name=Measurement,json=measurement,proto3" json:"Measurement,omitempty"`
Cells []*Cell `protobuf:"bytes,4,rep,name=Cells,json=cells" json:"Cells,omitempty"`
Autoflow bool `protobuf:"varint,5,opt,name=Autoflow,json=autoflow,proto3" json:"Autoflow,omitempty"`
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
Measurement string `protobuf:"bytes,3,opt,name=Measurement,proto3" json:"Measurement,omitempty"`
Cells []*Cell `protobuf:"bytes,4,rep,name=Cells" json:"Cells,omitempty"`
Autoflow bool `protobuf:"varint,5,opt,name=Autoflow,proto3" json:"Autoflow,omitempty"`
}
func (m *Layout) Reset() { *m = Layout{} }
@ -163,13 +170,13 @@ func (m *Cell) GetQueries() []*Query {
}
type Query struct {
Command string `protobuf:"bytes,1,opt,name=Command,json=command,proto3" json:"Command,omitempty"`
DB string `protobuf:"bytes,2,opt,name=DB,json=dB,proto3" json:"DB,omitempty"`
RP string `protobuf:"bytes,3,opt,name=RP,json=rP,proto3" json:"RP,omitempty"`
GroupBys []string `protobuf:"bytes,4,rep,name=GroupBys,json=groupBys" json:"GroupBys,omitempty"`
Wheres []string `protobuf:"bytes,5,rep,name=Wheres,json=wheres" json:"Wheres,omitempty"`
Label string `protobuf:"bytes,6,opt,name=Label,json=label,proto3" json:"Label,omitempty"`
Range *Range `protobuf:"bytes,7,opt,name=Range,json=range" json:"Range,omitempty"`
Command string `protobuf:"bytes,1,opt,name=Command,proto3" json:"Command,omitempty"`
DB string `protobuf:"bytes,2,opt,name=DB,proto3" json:"DB,omitempty"`
RP string `protobuf:"bytes,3,opt,name=RP,proto3" json:"RP,omitempty"`
GroupBys []string `protobuf:"bytes,4,rep,name=GroupBys" json:"GroupBys,omitempty"`
Wheres []string `protobuf:"bytes,5,rep,name=Wheres" json:"Wheres,omitempty"`
Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"`
Range *Range `protobuf:"bytes,7,opt,name=Range" json:"Range,omitempty"`
}
func (m *Query) Reset() { *m = Query{} }
@ -185,8 +192,8 @@ func (m *Query) GetRange() *Range {
}
type Range struct {
Upper int64 `protobuf:"varint,1,opt,name=Upper,json=upper,proto3" json:"Upper,omitempty"`
Lower int64 `protobuf:"varint,2,opt,name=Lower,json=lower,proto3" json:"Lower,omitempty"`
Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"`
Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"`
}
func (m *Range) Reset() { *m = Range{} }
@ -195,10 +202,10 @@ func (*Range) ProtoMessage() {}
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
type AlertRule struct {
ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
JSON string `protobuf:"bytes,2,opt,name=JSON,json=jSON,proto3" json:"JSON,omitempty"`
SrcID int64 `protobuf:"varint,3,opt,name=SrcID,json=srcID,proto3" json:"SrcID,omitempty"`
KapaID int64 `protobuf:"varint,4,opt,name=KapaID,json=kapaID,proto3" json:"KapaID,omitempty"`
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"`
SrcID int64 `protobuf:"varint,3,opt,name=SrcID,proto3" json:"SrcID,omitempty"`
KapaID int64 `protobuf:"varint,4,opt,name=KapaID,proto3" json:"KapaID,omitempty"`
}
func (m *AlertRule) Reset() { *m = AlertRule{} }
@ -207,8 +214,8 @@ func (*AlertRule) ProtoMessage() {}
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
type User struct {
ID uint64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Email string `protobuf:"bytes,2,opt,name=Email,json=email,proto3" json:"Email,omitempty"`
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Email string `protobuf:"bytes,2,opt,name=Email,proto3" json:"Email,omitempty"`
}
func (m *User) Reset() { *m = User{} }
@ -233,52 +240,50 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 750 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x6d, 0x6e, 0xdb, 0x46,
0x10, 0xc5, 0x8a, 0x5c, 0x8a, 0x5c, 0xb9, 0x6e, 0xb1, 0x30, 0x5a, 0xa2, 0xe8, 0x0f, 0x81, 0x68,
0x01, 0x15, 0x68, 0xfd, 0xc3, 0x3e, 0x81, 0x2c, 0x1a, 0x85, 0x5a, 0xd5, 0x76, 0x57, 0x56, 0xfb,
0xab, 0x05, 0xd6, 0xe2, 0xc8, 0x62, 0xbd, 0x22, 0xd9, 0x25, 0x59, 0x99, 0x47, 0x08, 0x90, 0x33,
0xe4, 0x10, 0xc9, 0x51, 0x72, 0x91, 0x1c, 0x21, 0xd8, 0xe1, 0xea, 0x0b, 0x4e, 0x02, 0x03, 0xf9,
0xf9, 0x66, 0x46, 0xc3, 0x37, 0xef, 0x3d, 0x8a, 0xec, 0x38, 0xcd, 0x2a, 0xd0, 0x99, 0x54, 0xa7,
0x85, 0xce, 0xab, 0x9c, 0xfb, 0x1b, 0x1c, 0xbd, 0x21, 0xac, 0x77, 0xf9, 0x58, 0xa8, 0x5c, 0xcb,
0x2a, 0xcd, 0x33, 0x7e, 0xcc, 0x3a, 0xe3, 0x38, 0x24, 0x7d, 0x32, 0x70, 0x44, 0x27, 0x8d, 0x39,
0x67, 0xee, 0x95, 0x5c, 0x41, 0xd8, 0xe9, 0x93, 0x41, 0x20, 0xdc, 0x4c, 0xae, 0x80, 0x7f, 0xcd,
0xbc, 0x59, 0x09, 0x7a, 0x1c, 0x87, 0x0e, 0xce, 0x79, 0x35, 0x22, 0x33, 0x1b, 0xcb, 0x4a, 0x86,
0x6e, 0x3b, 0x9b, 0xc8, 0x4a, 0xf2, 0xef, 0x58, 0x30, 0xd2, 0x20, 0x2b, 0x48, 0x86, 0x55, 0x48,
0x71, 0x3c, 0x98, 0x6f, 0x0a, 0xa6, 0x3b, 0x2b, 0x12, 0xdb, 0xf5, 0xda, 0x6e, 0xbd, 0x29, 0xf0,
0x90, 0x75, 0x63, 0x58, 0xc8, 0x5a, 0x55, 0x61, 0xb7, 0x4f, 0x06, 0xbe, 0xe8, 0x26, 0x2d, 0x8c,
0xde, 0x11, 0xe6, 0x4d, 0xf3, 0x5a, 0xcf, 0xe1, 0x59, 0x84, 0x39, 0x73, 0x6f, 0x9b, 0x02, 0x90,
0x6e, 0x20, 0xdc, 0xaa, 0x29, 0x80, 0x7f, 0xcb, 0x7c, 0x73, 0x84, 0xe9, 0x5b, 0xc2, 0x7e, 0x6d,
0xb1, 0xe9, 0xdd, 0xc8, 0xb2, 0x5c, 0xe7, 0x3a, 0x41, 0xce, 0x81, 0xf0, 0x0b, 0x8b, 0xf9, 0x57,
0xcc, 0x99, 0x89, 0x09, 0x92, 0x0d, 0x84, 0x53, 0x8b, 0xc9, 0xc7, 0x69, 0x9a, 0x3d, 0xb7, 0xa0,
0xe0, 0x5e, 0xcb, 0x45, 0xe8, 0xb7, 0x7b, 0x2a, 0x8b, 0xf9, 0x29, 0xe3, 0xe3, 0xac, 0x84, 0x79,
0xad, 0x61, 0xfa, 0x90, 0x16, 0x7f, 0x82, 0x4e, 0x17, 0x4d, 0x18, 0xe0, 0x02, 0x9e, 0x3e, 0xe9,
0x44, 0xff, 0xb0, 0x20, 0x96, 0xe5, 0xf2, 0x2e, 0x97, 0x3a, 0x79, 0xd6, 0xd1, 0x3f, 0x33, 0x3a,
0x07, 0xa5, 0xca, 0xd0, 0xe9, 0x3b, 0x83, 0xde, 0xd9, 0x37, 0xa7, 0xdb, 0x0c, 0x6c, 0xf7, 0x8c,
0x40, 0x29, 0xd1, 0x4e, 0x45, 0x2f, 0x08, 0xfb, 0xe2, 0xa0, 0xc1, 0x8f, 0x18, 0x79, 0xc4, 0x67,
0x50, 0x41, 0x1e, 0x0d, 0x6a, 0x70, 0x3f, 0x15, 0xa4, 0x31, 0x68, 0x8d, 0x72, 0x52, 0x41, 0xd6,
0x06, 0x2d, 0x51, 0x44, 0x2a, 0xc8, 0xd2, 0xe8, 0xf1, 0x5f, 0x0d, 0x3a, 0x85, 0x32, 0xa4, 0x7d,
0x67, 0x10, 0x88, 0x0d, 0x34, 0x34, 0x51, 0x6f, 0xef, 0xd0, 0x1b, 0xe3, 0x07, 0x4a, 0x67, 0xbd,
0x89, 0x5e, 0x1a, 0x7b, 0x41, 0xff, 0x0f, 0xfa, 0x59, 0x97, 0xee, 0x5b, 0xe9, 0x7c, 0xc2, 0x4a,
0xf7, 0xc3, 0x56, 0xd2, 0x9d, 0x95, 0x27, 0x8c, 0x4e, 0xf5, 0x7c, 0x1c, 0xdb, 0x2c, 0xd2, 0xd2,
0x80, 0xe8, 0x15, 0x61, 0xde, 0x44, 0x36, 0x79, 0x5d, 0xed, 0xd1, 0x09, 0x90, 0x4e, 0x9f, 0xf5,
0x86, 0x45, 0xa1, 0xd2, 0x39, 0xbe, 0x3d, 0x96, 0x55, 0x4f, 0xee, 0x4a, 0x66, 0xe2, 0x77, 0x90,
0x65, 0xad, 0x61, 0x05, 0x59, 0x65, 0xf9, 0xf5, 0x56, 0xbb, 0x12, 0xff, 0x9e, 0xd1, 0x11, 0x1a,
0xe5, 0xa2, 0x51, 0xc7, 0x3b, 0xa3, 0xf6, 0xfc, 0x31, 0x87, 0x0c, 0xeb, 0x2a, 0x5f, 0xa8, 0x7c,
0x8d, 0x8c, 0x7d, 0xe1, 0x4b, 0x8b, 0xa3, 0xb7, 0x84, 0xb9, 0x9f, 0x65, 0xd9, 0x8f, 0x87, 0x96,
0xf5, 0xce, 0xbe, 0xdc, 0x91, 0xf8, 0xa3, 0x06, 0xdd, 0xec, 0x3c, 0x3c, 0x62, 0x24, 0xb5, 0x06,
0x92, 0x74, 0xeb, 0x68, 0x77, 0xcf, 0x8e, 0x90, 0x75, 0x1b, 0x2d, 0xb3, 0x7b, 0x28, 0x43, 0xbf,
0xef, 0x0c, 0x1c, 0xb1, 0x81, 0xd8, 0x51, 0xf2, 0x0e, 0x54, 0x19, 0x06, 0x6d, 0x32, 0x2c, 0xdc,
0xa6, 0x80, 0xed, 0xa5, 0xe0, 0x35, 0x61, 0x14, 0x1f, 0x6e, 0x7e, 0x37, 0xca, 0x57, 0x2b, 0x99,
0x25, 0x56, 0xfa, 0xee, 0xbc, 0x85, 0xc6, 0x8f, 0xf8, 0xc2, 0xca, 0xde, 0x49, 0x2e, 0x0c, 0x16,
0x37, 0x56, 0xe4, 0x8e, 0xbe, 0x31, 0xaa, 0xfd, 0xa2, 0xf3, 0xba, 0xb8, 0x68, 0x5a, 0x79, 0x03,
0xe1, 0xdf, 0x5b, 0x6c, 0xfe, 0xc6, 0xfe, 0x5a, 0x82, 0xde, 0xc6, 0xd4, 0x5b, 0x23, 0x32, 0x21,
0x98, 0x18, 0x56, 0xf6, 0x4a, 0x8a, 0x14, 0xf9, 0x0f, 0x8c, 0x0a, 0x73, 0x05, 0x9e, 0x7a, 0x20,
0x10, 0x96, 0x05, 0xc5, 0x1b, 0xa3, 0x73, 0x3b, 0x66, 0xb6, 0xcc, 0x8a, 0x02, 0xb4, 0xcd, 0x2e,
0xad, 0x0d, 0xc0, 0xdd, 0xf9, 0x1a, 0x34, 0x52, 0x76, 0x04, 0x55, 0x06, 0x44, 0x7f, 0xb3, 0x60,
0xa8, 0x40, 0x57, 0xa2, 0x56, 0xf0, 0x24, 0x62, 0x9c, 0xb9, 0xbf, 0x4e, 0xaf, 0xaf, 0x36, 0x89,
0xff, 0x77, 0x7a, 0x7d, 0xb5, 0xcb, 0xa9, 0xb3, 0x97, 0x53, 0x73, 0xd0, 0x6f, 0xb2, 0x90, 0xe3,
0x18, 0x8d, 0x75, 0x84, 0xf7, 0x80, 0x28, 0xfa, 0x89, 0xb9, 0xe6, 0xfd, 0xd8, 0xdb, 0xec, 0xe2,
0xe6, 0x13, 0x46, 0x2f, 0x57, 0x32, 0x55, 0x76, 0x35, 0x05, 0x03, 0xee, 0x3c, 0xfc, 0x44, 0x9c,
0xbf, 0x0f, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x83, 0xb5, 0x2a, 0x34, 0x06, 0x00, 0x00,
// 712 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0xd1, 0x6e, 0xd3, 0x4a,
0x10, 0xd5, 0xc6, 0x76, 0x12, 0x4f, 0x7b, 0x7b, 0xaf, 0x56, 0xd5, 0xc5, 0x42, 0x3c, 0x44, 0x16,
0x48, 0x41, 0x82, 0x3e, 0xd0, 0x2f, 0x48, 0xe3, 0x0a, 0x05, 0x4a, 0x29, 0x9b, 0x06, 0x9e, 0x40,
0xda, 0x26, 0x9b, 0xc6, 0xc2, 0xb1, 0xcd, 0xda, 0x26, 0xf5, 0x3f, 0xf0, 0x05, 0x3c, 0xf0, 0x11,
0xf0, 0x29, 0xfc, 0x08, 0x9f, 0x80, 0x66, 0xbc, 0x76, 0x5c, 0x51, 0x50, 0x9f, 0x78, 0x9b, 0x33,
0x33, 0x9d, 0x3d, 0x73, 0xce, 0xb8, 0x81, 0xbd, 0x30, 0xce, 0x95, 0x8e, 0x65, 0x74, 0x90, 0xea,
0x24, 0x4f, 0x78, 0xbf, 0xc6, 0xfe, 0x37, 0x06, 0x3b, 0xc7, 0x57, 0x69, 0x94, 0x68, 0x99, 0x87,
0x49, 0xcc, 0xf7, 0xa0, 0x33, 0x09, 0x3c, 0x36, 0x60, 0x43, 0x4b, 0x74, 0x26, 0x01, 0xe7, 0x60,
0x9f, 0xca, 0xb5, 0xf2, 0x3a, 0x03, 0x36, 0x74, 0x05, 0xc5, 0xfc, 0x7f, 0xe8, 0xce, 0x32, 0xa5,
0x27, 0x81, 0x67, 0x51, 0x9f, 0x41, 0xd8, 0x1b, 0xc8, 0x5c, 0x7a, 0x76, 0xd5, 0x8b, 0x31, 0xbf,
0x07, 0xee, 0x58, 0x2b, 0x99, 0xab, 0xc5, 0x28, 0xf7, 0x1c, 0x6a, 0xdf, 0x26, 0xb0, 0x3a, 0x4b,
0x17, 0xa6, 0xda, 0xad, 0xaa, 0x4d, 0x82, 0x7b, 0xd0, 0x0b, 0xd4, 0x52, 0x16, 0x51, 0xee, 0xf5,
0x06, 0x6c, 0xd8, 0x17, 0x35, 0xf4, 0x7f, 0x30, 0xe8, 0x4e, 0x93, 0x42, 0xcf, 0xd5, 0xad, 0x08,
0x73, 0xb0, 0xcf, 0xcb, 0x54, 0x11, 0x5d, 0x57, 0x50, 0xcc, 0xef, 0x42, 0x1f, 0x69, 0xc7, 0xd8,
0x5b, 0x11, 0x6e, 0x30, 0xd6, 0xce, 0x64, 0x96, 0x6d, 0x12, 0xbd, 0x20, 0xce, 0xae, 0x68, 0x30,
0xff, 0x0f, 0xac, 0x99, 0x38, 0x21, 0xb2, 0xae, 0xc0, 0xf0, 0xf7, 0x34, 0x71, 0xce, 0xb9, 0x8a,
0xd4, 0xa5, 0x96, 0x4b, 0xaf, 0x5f, 0xcd, 0xa9, 0x31, 0x3f, 0x00, 0x3e, 0x89, 0x33, 0x35, 0x2f,
0xb4, 0x9a, 0xbe, 0x0f, 0xd3, 0xd7, 0x4a, 0x87, 0xcb, 0xd2, 0x73, 0x69, 0xc0, 0x0d, 0x15, 0xff,
0x1d, 0xb8, 0x81, 0xcc, 0x56, 0x17, 0x89, 0xd4, 0x8b, 0x5b, 0x2d, 0xfd, 0x18, 0x9c, 0xb9, 0x8a,
0xa2, 0xcc, 0xb3, 0x06, 0xd6, 0x70, 0xe7, 0xc9, 0x9d, 0x83, 0xe6, 0x06, 0x9a, 0x39, 0x63, 0x15,
0x45, 0xa2, 0xea, 0xf2, 0x3f, 0x33, 0xf8, 0xe7, 0x5a, 0x81, 0xef, 0x02, 0xbb, 0xa2, 0x37, 0x1c,
0xc1, 0xae, 0x10, 0x95, 0x34, 0xdf, 0x11, 0xac, 0x44, 0xb4, 0x21, 0x39, 0x1d, 0xc1, 0x36, 0x88,
0x56, 0x24, 0xa2, 0x23, 0xd8, 0x8a, 0x3f, 0x84, 0xde, 0x87, 0x42, 0xe9, 0x50, 0x65, 0x9e, 0x43,
0x4f, 0xff, 0xbb, 0x7d, 0xfa, 0x55, 0xa1, 0x74, 0x29, 0xea, 0x3a, 0xf2, 0x26, 0x03, 0x2a, 0x35,
0x29, 0xc6, 0x5c, 0x8e, 0x66, 0xf5, 0xaa, 0x1c, 0xc6, 0xfe, 0x27, 0xf4, 0x5b, 0xe9, 0x8f, 0x4a,
0xdf, 0x6a, 0xf5, 0xb6, 0xb7, 0xd6, 0x1f, 0xbc, 0xb5, 0x6f, 0xf6, 0xd6, 0xd9, 0x7a, 0xbb, 0x0f,
0xce, 0x54, 0xcf, 0x27, 0x81, 0x39, 0xce, 0x0a, 0xf8, 0x5f, 0x18, 0x74, 0x4f, 0x64, 0x99, 0x14,
0x79, 0x8b, 0x8e, 0x4b, 0x74, 0x06, 0xb0, 0x33, 0x4a, 0xd3, 0x28, 0x9c, 0xd3, 0xe7, 0x64, 0x58,
0xb5, 0x53, 0xd8, 0xf1, 0x42, 0xc9, 0xac, 0xd0, 0x6a, 0xad, 0xe2, 0xdc, 0xf0, 0x6b, 0xa7, 0xf8,
0x7d, 0x70, 0xc6, 0xe4, 0x9c, 0x4d, 0xf2, 0xed, 0x6d, 0xe5, 0xab, 0x0c, 0xa3, 0x22, 0x2e, 0x32,
0x2a, 0xf2, 0x64, 0x19, 0x25, 0x1b, 0x62, 0xdc, 0x17, 0x0d, 0xf6, 0xbf, 0x33, 0xb0, 0xff, 0x96,
0x87, 0xbb, 0xc0, 0x42, 0x63, 0x20, 0x0b, 0x1b, 0x47, 0x7b, 0x2d, 0x47, 0x3d, 0xe8, 0x95, 0x5a,
0xc6, 0x97, 0x2a, 0xf3, 0xfa, 0x03, 0x6b, 0x68, 0x89, 0x1a, 0x52, 0x25, 0x92, 0x17, 0x2a, 0xca,
0x3c, 0x77, 0x60, 0x0d, 0x5d, 0x51, 0xc3, 0xe6, 0x0a, 0xa0, 0x75, 0x05, 0x5f, 0x19, 0x38, 0xf4,
0x38, 0xfe, 0xdd, 0x38, 0x59, 0xaf, 0x65, 0xbc, 0x30, 0xd2, 0xd7, 0x10, 0xfd, 0x08, 0x8e, 0x8c,
0xec, 0x9d, 0xe0, 0x08, 0xb1, 0x38, 0x33, 0x22, 0x77, 0xc4, 0x19, 0xaa, 0xf6, 0x54, 0x27, 0x45,
0x7a, 0x54, 0x56, 0xf2, 0xba, 0xa2, 0xc1, 0xf8, 0x7f, 0xed, 0xcd, 0x4a, 0x69, 0xb3, 0xb3, 0x2b,
0x0c, 0xc2, 0x23, 0x38, 0x41, 0x56, 0x66, 0xcb, 0x0a, 0xf0, 0x07, 0xe0, 0x08, 0xdc, 0x82, 0x56,
0xbd, 0x26, 0x10, 0xa5, 0x45, 0x55, 0xf5, 0x0f, 0x4d, 0x1b, 0x4e, 0x99, 0xa5, 0xa9, 0xd2, 0xe6,
0x76, 0x2b, 0x40, 0xb3, 0x93, 0x8d, 0xd2, 0x44, 0xd9, 0x12, 0x15, 0xf0, 0xdf, 0x82, 0x3b, 0x8a,
0x94, 0xce, 0x45, 0x11, 0xa9, 0x5f, 0x4e, 0x8c, 0x83, 0xfd, 0x6c, 0xfa, 0xf2, 0xb4, 0xbe, 0x78,
0x8c, 0xb7, 0x77, 0x6a, 0xb5, 0xee, 0x14, 0x17, 0x7a, 0x2e, 0x53, 0x39, 0x09, 0xc8, 0x58, 0x4b,
0x18, 0xe4, 0x3f, 0x02, 0x1b, 0xbf, 0x87, 0xd6, 0x64, 0x9b, 0x26, 0xef, 0x83, 0x73, 0xbc, 0x96,
0x61, 0x64, 0x46, 0x57, 0xe0, 0xa2, 0x4b, 0xbf, 0x19, 0x87, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff,
0x6d, 0xf2, 0xe7, 0x54, 0x45, 0x06, 0x00, 0x00,
}

View File

@ -34,7 +34,7 @@ message DashboardCell {
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 string queries = 5; // Time-series data queries for 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
}

View File

@ -243,7 +243,7 @@ type DashboardCell struct {
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []string `json:"queries"`
Queries []Query `json:"queries"`
Type string `json:"type"`
}

View File

@ -3,7 +3,7 @@ machine:
services:
- docker
environment:
DOCKER_TAG: chronograf-20161207
DOCKER_TAG: chronograf-20170127
dependencies:
override:
@ -26,7 +26,7 @@ deployment:
--package
--platform all
--arch all
--upload
--upload-overwrite
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- docker build -t chronograf .
@ -46,7 +46,7 @@ deployment:
--package
--platform all
--arch all
--upload
--upload-overwrite
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
@ -67,7 +67,7 @@ deployment:
--package
--platform all
--arch all
--upload
--upload-overwrite
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .

View File

@ -16,9 +16,9 @@ RUN pip install boto requests python-jose --upgrade
RUN gem install fpm
# Install node
RUN wget -q https://nodejs.org/dist/latest-v6.x/node-v6.9.1-linux-x64.tar.gz; \
tar -xvf node-v6.9.1-linux-x64.tar.gz -C / --strip-components=1; \
rm -f node-v6.9.1-linux-x64.tar.gz
RUN wget -q https://nodejs.org/dist/latest-v6.x/node-v6.9.4-linux-x64.tar.gz; \
tar -xvf node-v6.9.4-linux-x64.tar.gz -C / --strip-components=1; \
rm -f node-v6.9.4-linux-x64.tar.gz
# Update npm
RUN cd $(npm root -g)/npm \
@ -28,7 +28,7 @@ RUN npm install npm -g
# Install go
ENV GOPATH /root/go
ENV GO_VERSION 1.7.4
ENV GO_VERSION 1.7.5
ENV GO_ARCH amd64
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \

12
etc/README.md Normal file
View File

@ -0,0 +1,12 @@
## Builds
Builds are run from a docker build image that is configured with the node and go we support.
Our circle.yml uses this docker container to build, test and create release packages.
### Updating new node/go versions
After updating the Dockerfile_build run
`docker build -t quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d") -f Dockerfile_build`
and push to quay with:
`docker push quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d")`

View File

@ -164,10 +164,8 @@ func ValidDashboardRequest(d chronograf.Dashboard) error {
}
for _, c := range d.Cells {
for _, q := range c.Queries {
if len(q) == 0 {
return fmt.Errorf("query required")
}
if (len(c.Queries) == 0) {
return fmt.Errorf("query required")
}
}

View File

@ -119,8 +119,8 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.POST("/chronograf/v1/dashboards", service.NewDashboard)
router.GET("/chronograf/v1/dashboards/:id", service.DashboardID)
router.DELETE("/chronograf/v1/dashboard/:id", service.RemoveDashboard)
router.PUT("/chronograf/v1/dashboard/:id", service.UpdateDashboard)
router.DELETE("/chronograf/v1/dashboards/:id", service.RemoveDashboard)
router.PUT("/chronograf/v1/dashboards/:id", service.UpdateDashboard)
/* Authentication */
if opts.UseAuth {

View File

@ -6,3 +6,10 @@ export function getDashboards() {
url: `/chronograf/v1/dashboards`,
});
}
export function getDashboard(id) {
return AJAX({
method: 'GET',
url: `/chronograf/v1/dashboards/${id}`,
});
}

View File

@ -0,0 +1,111 @@
import React, {PropTypes} from 'react';
import ReactTooltip from 'react-tooltip';
import LayoutRenderer from 'shared/components/LayoutRenderer';
import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown';
import timeRanges from 'hson!../../shared/data/timeRanges.hson';
import {getDashboard} from '../apis';
import {getSource} from 'shared/apis';
const DashboardPage = React.createClass({
propTypes: {
params: PropTypes.shape({
sourceID: PropTypes.string.isRequired,
dashboardID: PropTypes.string.isRequired,
}).isRequired,
},
getInitialState() {
const fifteenMinutesIndex = 1;
return {
timeRange: timeRanges[fifteenMinutesIndex],
};
},
componentDidMount() {
getDashboard(this.props.params.dashboardID).then((resp) => {
getSource(this.props.params.sourceID).then(({data: source}) => {
this.setState({
dashboard: resp.data,
source,
});
});
});
},
renderDashboard(dashboard) {
const autoRefreshMs = 15000;
const {timeRange} = this.state;
const {source} = this.state;
const cellWidth = 4;
const cellHeight = 4;
const cells = dashboard.cells.map((cell, i) => {
const dashboardCell = Object.assign(cell, {
w: cellWidth,
h: cellHeight,
queries: cell.queries,
i: i.toString(),
});
dashboardCell.queries.forEach((q) => {
q.text = q.query;
q.database = source.telegraf;
});
return dashboardCell;
});
return (
<LayoutRenderer
timeRange={timeRange}
cells={cells}
autoRefreshMs={autoRefreshMs}
source={source.links.proxy}
/>
);
},
handleChooseTimeRange({lower}) {
const timeRange = timeRanges.find((range) => range.queryValue === lower);
this.setState({timeRange});
},
render() {
const {dashboard, timeRange} = this.state;
const dashboardName = dashboard ? dashboard.name : '';
return (
<div className="page">
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
<div className="dropdown page-header-dropdown">
<button className="dropdown-toggle" type="button" data-toggle="dropdown">
<span className="button-text">{dashboardName}</span>
</button>
</div>
</div>
<div className="page-header__right">
<div className="btn btn-info btn-sm" data-for="graph-tips-tooltip" data-tip="<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>">
<span className="icon heart"></span>
Graph Tips
</div>
<ReactTooltip id="graph-tips-tooltip" effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip place-bottom" />
<TimeRangeDropdown onChooseTimeRange={this.handleChooseTimeRange} selected={timeRange.inputValue} />
</div>
</div>
</div>
<div className="page-contents">
<div className="container-fluid full-width">
{ dashboard ? this.renderDashboard(dashboard) : '' }
</div>
</div>
</div>
);
},
});
export default DashboardPage;

View File

@ -1,26 +1,44 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {getDashboards} from '../apis';
const DashboardsPage = React.createClass({
propTypes: {
source: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string,
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
}).isRequired,
telegraf: PropTypes.string.isRequired,
}),
addFlashMessage: PropTypes.func,
},
getInitialState() {
return {
dashboards: [],
waiting: true,
};
},
componentDidMount() {
getDashboards().then((dashboards) => {
getDashboards().then((resp) => {
this.setState({
dashboards,
dashboards: resp.data.dashboards,
waiting: false,
});
});
},
render() {
let tableHeader;
if (this.state.dashboards.length === 0) {
if (this.state.waiting) {
tableHeader = "Loading Dashboards...";
} else if (this.state.dashboards.length === 0) {
tableHeader = "No Dashboards";
} else {
tableHeader = `${this.state.dashboards.length} Dashboards`;
}
@ -56,7 +74,11 @@ const DashboardsPage = React.createClass({
this.state.dashboards.map((dashboard) => {
return (
<tr key={dashboard.id}>
<td className="monotype">{dashboard.name}</td>
<td className="monotype">
<Link to={`/sources/${this.props.source.id}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
</tr>
);
})

View File

@ -1,2 +1,3 @@
import DashboardsPage from './containers/DashboardsPage';
export {DashboardsPage};
import DashboardPage from './containers/DashboardPage';
export {DashboardsPage, DashboardPage};

View File

@ -12,7 +12,7 @@ import {KubernetesPage} from 'src/kubernetes';
import {Login} from 'src/auth';
import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor';
import DataExplorer from 'src/data_explorer';
import {DashboardsPage} from 'src/dashboards';
import {DashboardsPage, DashboardPage} from 'src/dashboards';
import {CreateSource, SourcePage, ManageSources} from 'src/sources';
import NotFound from 'src/shared/components/NotFound';
import configureStore from 'src/store/configureStore';
@ -120,6 +120,7 @@ const Root = React.createClass({
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
<Route path="alerts" component={AlertsApp} />
<Route path="dashboards" component={DashboardsPage} />
<Route path="dashboards/:dashboardID" component={DashboardPage} />
<Route path="alert-rules" component={KapacitorRulesPage} />
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
<Route path="alert-rules/new" component={KapacitorRulePage} />

View File

@ -3,6 +3,7 @@ import _ from 'lodash';
import {getKapacitorConfig, updateKapacitorConfigSection, testAlertOutput} from 'shared/apis';
import AlertaConfig from './AlertaConfig';
import HipChatConfig from './HipChatConfig';
import OpsGenieConfig from './OpsGenieConfig';
import PagerDutyConfig from './PagerDutyConfig';
import SensuConfig from './SensuConfig';
import SlackConfig from './SlackConfig';
@ -111,6 +112,7 @@ const AlertOutputs = React.createClass({
<label htmlFor="alert-endpoint" className="sr-only">Alert Enpoint</label>
<select value={this.state.selectedEndpoint} className="form-control" id="source" onChange={this.changeSelectedEndpoint}>
<option value="hipchat">HipChat</option>
<option value="opsgenie">OpsGenie</option>
<option value="pagerduty">PagerDuty</option>
<option value="sensu">Sensu</option>
<option value="slack">Slack</option>
@ -159,6 +161,9 @@ const AlertOutputs = React.createClass({
case 'telegram': {
return <TelegramConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}
case 'opsgenie': {
return <OpsGenieConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}
case 'pagerduty': {
return <PagerDutyConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}

View File

@ -0,0 +1,177 @@
import React, {PropTypes} from 'react';
import _ from 'lodash';
const {
array,
arrayOf,
bool,
func,
shape,
string,
} = PropTypes;
const OpsGenieConfig = React.createClass({
propTypes: {
config: shape({
options: shape({
'api-key': bool,
teams: array,
recipients: array,
}).isRequired,
}).isRequired,
onSave: func.isRequired,
},
getInitialState() {
const {teams, recipients} = this.props.config.options;
return {
currentTeams: teams || [],
currentRecipients: recipients || [],
};
},
handleSaveAlert(e) {
e.preventDefault();
const properties = {
'api-key': this.apiKey.value,
teams: this.state.currentTeams,
recipients: this.state.currentRecipients,
};
this.props.onSave(properties);
},
handleAddTeam(team) {
this.setState({currentTeams: this.state.currentTeams.concat(team)});
},
handleAddRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.concat(recipient)});
},
handleDeleteTeam(team) {
this.setState({currentTeams: this.state.currentTeams.filter(t => t !== team)});
},
handleDeleteRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.filter(r => r !== recipient)});
},
render() {
const {options} = this.props.config;
const apiKey = options['api-key'];
const {currentTeams, currentRecipients} = this.state;
return (
<div>
<h4 className="text-center">OpsGenie Alert</h4>
<br/>
<p>Have alerts sent to OpsGenie.</p>
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="api-key">API Key</label>
<input className="form-control" id="api-key" type="text" ref={(r) => this.apiKey = r} defaultValue={apiKey || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates the OpsGenie API key has been set</label>
</div>
<TagInput title="Teams" onAddTag={this.handleAddTeam} onDeleteTag={this.handleDeleteTeam} tags={currentTeams} />
<TagInput title="Recipients" onAddTag={this.handleAddRecipient} onDeleteTag={this.handleDeleteRecipient} tags={currentRecipients} />
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className="btn btn-block btn-primary" type="submit">Save</button>
</div>
</form>
</div>
);
},
});
const TagInput = React.createClass({
propTypes: {
onAddTag: func.isRequired,
onDeleteTag: func.isRequired,
tags: arrayOf(string).isRequired,
title: string.isRequired,
},
handleAddTag(e) {
if (e.key === 'Enter') {
e.preventDefault();
const newItem = e.target.value.trim();
const {tags, onAddTag} = this.props;
if (!this.shouldAddToList(newItem, tags)) {
return;
}
this.input.value = '';
onAddTag(newItem);
}
},
shouldAddToList(item, tags) {
return (!_.isEmpty(item) && !tags.find(l => l === item));
},
render() {
const {title, tags, onDeleteTag} = this.props;
return (
<div className="form-group col-xs-12">
<label htmlFor={title}>{title}</label>
<input
placeholder={`Type and hit 'Enter' to add to list of ${title}`}
autoComplete="off"
className="form-control"
id={title} type="text"
ref={(r) => this.input = r}
onKeyDown={this.handleAddTag}>
</input>
<Tags tags={tags} onDeleteTag={onDeleteTag}/>
</div>
);
},
});
const Tags = React.createClass({
propTypes: {
tags: arrayOf(string),
onDeleteTag: func,
},
render() {
const {tags, onDeleteTag} = this.props;
return (
<div className="input-tag-list">
{
tags.map((item) => {
return (
<Tag key={item} item={item} onDelete={onDeleteTag} />
);
})
}
</div>
);
},
});
const Tag = React.createClass({
propTypes: {
item: string,
onDelete: func,
},
render() {
const {item, onDelete} = this.props;
return (
<span key={item} className="input-tag-item">
<span>{item}</span>
<span className="icon remove" onClick={() => onDelete(item)}></span>
</span>
);
},
});
export default OpsGenieConfig;

View File

@ -18,11 +18,19 @@ const TelegramConfig = React.createClass({
handleSaveAlert(e) {
e.preventDefault();
let parseMode;
if (this.parseModeHTML.checked) {
parseMode = 'HTML';
}
if (this.parseModeMarkdown.checked) {
parseMode = 'Markdown';
}
const properties = {
chatID: this.chatID.value,
disableNotification: this.disableNotification.checked,
disableWebPagePreview: this.disableWebPagePreview.checked,
parseMode: this.parseMode.checked,
'chat-id': this.chatID.value,
'disable-notification': this.disableNotification.checked,
'disable-web-page-preview': this.disableWebPagePreview.checked,
'parse-mode': parseMode,
token: this.token.value,
url: this.url.value,
};
@ -61,9 +69,16 @@ const TelegramConfig = React.createClass({
</div>
<div className="form-group col-xs-12">
<label htmlFor="parseMode">Parse Mode</label>
<div className="form-control-static">
<input id="enableParseMode" type="checkbox" defaultChecked={parseMode} ref={(r) => this.parseMode = r} />
<label htmlFor="enableParseMode">Enable Parse Mode</label>
<div className="radio">
<input id="parseModeHTML" type="radio" name="parseMode" value="html" defaultChecked={parseMode === 'HTML'} ref={(r) => this.parseModeHTML = r} />
<label htmlFor="parseModeHTML">HTML</label>
</div>
<div className="radio">
<input id="parseModeMarkdown" type="radio" name="parseMode" value="markdown" defaultChecked={parseMode === 'Markdown'} ref={(r) => this.parseModeMarkdown = r} />
<label htmlFor="parseModeMarkdown">Markdown</label>
</div>
</div>
</div>

View File

@ -26,6 +26,7 @@
@import 'layout/sidebar.scss';
// Components
@import 'components/input-tag-list';
@import 'components/group-by-time-dropdown';
@import 'components/page-header-dropdown';
@import 'components/multi-select-dropdown';

View File

@ -0,0 +1,32 @@
.input-tag-list {
padding: 0 11px;
margin: 2px 0px;
font-size: 0;
}
$input-tag-item-height: 24px;
.input-tag-item {
display: inline-block;
white-space: nowrap;
height: $input-tag-item-height;
line-height: $input-tag-item-height;
padding: 0 9px;
border-radius: 3px;
font-size: 12px;
font-weight: 600;
background-color: $g5-pepper;
color: $g18-cloud;
cursor: default;
margin: 2px;
}
.input-tag-item .icon {
transition: color 0.25s ease;
margin-left: 6px;
position: relative;
top: -0.5px;
&:hover {
color: $c-dreamsicle;
cursor: pointer;
}
}

View File

@ -528,6 +528,83 @@ $form-static-checkbox-size: 16px;
&:checked + label {
color: $g20-white;
&:after {
opacity: 1;
transform: translate(-50%,-50%) scale(1,1);
}
}
}
}
.form-control-static .radio {
margin: 0;
input[type="radio"] {
position: relative;
left: -9999px;
visibility: hidden;
width: 0;
height: 0;
margin: 0;
// Faux Checkbox
& + label {
font-size: 14px !important;
line-height: 16px;
color: $g11-sidewalk;
font-weight: 500;
transition: color 0.25s ease;
margin: 0;
padding: 0 0 0 (12px + $form-static-checkbox-size) !important;
user-select: none;
-ms-user-select: none;
-moz-user-selct: none;
-webkit-user-select: none;
&:before {
content: '';
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: $form-static-checkbox-size;
height: $form-static-checkbox-size;
background-color: $g2-kevlar;
border: 2px solid $g5-pepper;
border-radius: 50%;
z-index: 2;
transition:
border-color 0.25s ease;
}
&:after {
content: '';
position: absolute;
top: 50%;
left: ($form-static-checkbox-size / 2);
transform: translate(-50%,-50%) scale(2,2);
opacity: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: $c-pool;
z-index: 3;
transition:
opacity 0.25s ease,
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&:hover {
cursor: pointer;
color: $g20-white;
&:before {
border-color: $g6-smoke;
}
}
}
// Faux Checkbox (Checked)
&:checked + label {
color: $g20-white;
&:after {
opacity: 1;
transform: translate(-50%,-50%) scale(1,1);