Merge pull request #691 from influxdata/feature/dashboards

Feature/dashboards
pull/698/head
Jade McGough 2016-12-15 13:54:23 -08:00 committed by GitHub
commit 1be3eef2e2
12 changed files with 782 additions and 62 deletions

View File

@ -21,6 +21,7 @@ type Client struct {
LayoutStore *LayoutStore LayoutStore *LayoutStore
UsersStore *UsersStore UsersStore *UsersStore
AlertsStore *AlertsStore AlertsStore *AlertsStore
DashboardsStore *DashboardsStore
} }
func NewClient() *Client { func NewClient() *Client {
@ -34,6 +35,7 @@ func NewClient() *Client {
client: c, client: c,
IDs: &uuid.V4{}, IDs: &uuid.V4{},
} }
c.DashboardsStore = &DashboardsStore{client: c}
return c return c
} }
@ -63,6 +65,10 @@ func (c *Client) Open() error {
if _, err := tx.CreateBucketIfNotExists(LayoutBucket); err != nil { if _, err := tx.CreateBucketIfNotExists(LayoutBucket); err != nil {
return err return err
} }
// Always create Dashboards bucket.
if _, err := tx.CreateBucketIfNotExists(DashboardBucket); err != nil {
return err
}
// Always create Alerts bucket. // Always create Alerts bucket.
if _, err := tx.CreateBucketIfNotExists(AlertsBucket); err != nil { if _, err := tx.CreateBucketIfNotExists(AlertsBucket); err != nil {
return err return err

118
bolt/dashboards.go Normal file
View File

@ -0,0 +1,118 @@
package bolt
import (
"context"
"strconv"
"github.com/boltdb/bolt"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/bolt/internal"
)
// Ensure DashboardsStore implements chronograf.DashboardsStore.
var _ chronograf.DashboardsStore = &DashboardsStore{}
var DashboardBucket = []byte("Dashoard")
type DashboardsStore struct {
client *Client
IDs chronograf.DashboardID
}
// All returns all known dashboards
func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) {
var srcs []chronograf.Dashboard
if err := d.client.db.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(DashboardBucket).ForEach(func(k, v []byte) error {
var src chronograf.Dashboard
if err := internal.UnmarshalDashboard(v, &src); err != nil {
return err
}
srcs = append(srcs, src)
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return srcs, nil
}
// Add creates a new Dashboard in the DashboardsStore
func (d *DashboardsStore) Add(ctx context.Context, src chronograf.Dashboard) (chronograf.Dashboard, error) {
if err := d.client.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(DashboardBucket)
id, _ := b.NextSequence()
src.ID = chronograf.DashboardID(id)
strID := strconv.Itoa(int(id))
if v, err := internal.MarshalDashboard(src); err != nil {
return err
} else if err := b.Put([]byte(strID), v); err != nil {
return err
}
return nil
}); err != nil {
return chronograf.Dashboard{}, err
}
return src, nil
}
// Get returns a Dashboard if the id exists.
func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) {
var src chronograf.Dashboard
if err := d.client.db.View(func(tx *bolt.Tx) error {
strID := strconv.Itoa(int(id))
if v := tx.Bucket(DashboardBucket).Get([]byte(strID)); v == nil {
return chronograf.ErrDashboardNotFound
} else if err := internal.UnmarshalDashboard(v, &src); err != nil {
return err
}
return nil
}); err != nil {
return chronograf.Dashboard{}, err
}
return src, nil
}
// Delete the dashboard from DashboardsStore
func (s *DashboardsStore) Delete(ctx context.Context, d chronograf.Dashboard) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
if err := tx.Bucket(DashboardBucket).Delete(itob(int(d.ID))); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Update the dashboard in DashboardsStore
func (s *DashboardsStore) Update(ctx context.Context, d chronograf.Dashboard) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
// Get an existing dashboard with the same ID.
b := tx.Bucket(DashboardBucket)
strID := strconv.Itoa(int(d.ID))
if v := b.Get([]byte(strID)); v == nil {
return chronograf.ErrDashboardNotFound
}
if v, err := internal.MarshalDashboard(d); err != nil {
return err
} else if err := b.Put([]byte(strID), v); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -188,6 +188,56 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error {
return nil return nil
} }
// MarshalDashboard encodes a dashboard to binary protobuf format.
func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
cells := make([]*DashboardCell, len(d.Cells))
for i, c := range d.Cells {
cells[i] = &DashboardCell{
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: c.Queries,
Type: c.Type,
}
}
return proto.Marshal(&Dashboard{
ID: int64(d.ID),
Cells: cells,
Name: d.Name,
})
}
// UnmarshalDashboard decodes a layout from binary protobuf data.
func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
var pb Dashboard
if err := proto.Unmarshal(data, &pb); err != nil {
return err
}
cells := make([]chronograf.DashboardCell, len(d.Cells))
for i, c := range d.Cells {
cells[i] = chronograf.DashboardCell{
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: c.Queries,
Type: c.Type,
}
}
d.ID = chronograf.DashboardID(pb.ID)
d.Cells = cells
d.Name = pb.Name
return nil
}
// ScopedAlert contains the source and the kapacitor id // ScopedAlert contains the source and the kapacitor id
type ScopedAlert struct { type ScopedAlert struct {
chronograf.AlertRule chronograf.AlertRule

View File

@ -11,6 +11,8 @@ It is generated from these files:
It has these top-level messages: It has these top-level messages:
Exploration Exploration
Source Source
Dashboard
DashboardCell
Server Server
Layout Layout
Cell Cell
@ -67,6 +69,39 @@ func (m *Source) String() string { return proto.CompactTextString(m)
func (*Source) ProtoMessage() {} func (*Source) ProtoMessage() {}
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} } func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
type Dashboard struct {
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"`
}
func (m *Dashboard) Reset() { *m = Dashboard{} }
func (m *Dashboard) String() string { return proto.CompactTextString(m) }
func (*Dashboard) ProtoMessage() {}
func (*Dashboard) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} }
func (m *Dashboard) GetCells() []*DashboardCell {
if m != nil {
return m.Cells
}
return nil
}
type DashboardCell struct {
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
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"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"`
}
func (m *DashboardCell) Reset() { *m = DashboardCell{} }
func (m *DashboardCell) String() string { return proto.CompactTextString(m) }
func (*DashboardCell) ProtoMessage() {}
func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
type Server struct { type Server struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,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"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
@ -79,7 +114,7 @@ type Server struct {
func (m *Server) Reset() { *m = Server{} } func (m *Server) Reset() { *m = Server{} }
func (m *Server) String() string { return proto.CompactTextString(m) } func (m *Server) String() string { return proto.CompactTextString(m) }
func (*Server) ProtoMessage() {} func (*Server) ProtoMessage() {}
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} } func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
type Layout struct { type Layout struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -92,7 +127,7 @@ type Layout struct {
func (m *Layout) Reset() { *m = Layout{} } func (m *Layout) Reset() { *m = Layout{} }
func (m *Layout) String() string { return proto.CompactTextString(m) } func (m *Layout) String() string { return proto.CompactTextString(m) }
func (*Layout) ProtoMessage() {} func (*Layout) ProtoMessage() {}
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (m *Layout) GetCells() []*Cell { func (m *Layout) GetCells() []*Cell {
if m != nil { if m != nil {
@ -117,7 +152,7 @@ type Cell struct {
func (m *Cell) Reset() { *m = Cell{} } func (m *Cell) Reset() { *m = Cell{} }
func (m *Cell) String() string { return proto.CompactTextString(m) } func (m *Cell) String() string { return proto.CompactTextString(m) }
func (*Cell) ProtoMessage() {} func (*Cell) ProtoMessage() {}
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (m *Cell) GetQueries() []*Query { func (m *Cell) GetQueries() []*Query {
if m != nil { if m != nil {
@ -139,7 +174,7 @@ type Query struct {
func (m *Query) Reset() { *m = Query{} } func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto.CompactTextString(m) } func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {} func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
func (m *Query) GetRange() *Range { func (m *Query) GetRange() *Range {
if m != nil { if m != nil {
@ -156,7 +191,7 @@ type Range struct {
func (m *Range) Reset() { *m = Range{} } func (m *Range) Reset() { *m = Range{} }
func (m *Range) String() string { return proto.CompactTextString(m) } func (m *Range) String() string { return proto.CompactTextString(m) }
func (*Range) ProtoMessage() {} func (*Range) ProtoMessage() {}
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
type AlertRule struct { type AlertRule struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -168,7 +203,7 @@ type AlertRule struct {
func (m *AlertRule) Reset() { *m = AlertRule{} } func (m *AlertRule) Reset() { *m = AlertRule{} }
func (m *AlertRule) String() string { return proto.CompactTextString(m) } func (m *AlertRule) String() string { return proto.CompactTextString(m) }
func (*AlertRule) ProtoMessage() {} func (*AlertRule) ProtoMessage() {}
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
type User struct { type User struct {
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -178,11 +213,13 @@ type User struct {
func (m *User) Reset() { *m = User{} } func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) } func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {} func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func init() { func init() {
proto.RegisterType((*Exploration)(nil), "internal.Exploration") proto.RegisterType((*Exploration)(nil), "internal.Exploration")
proto.RegisterType((*Source)(nil), "internal.Source") proto.RegisterType((*Source)(nil), "internal.Source")
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
proto.RegisterType((*Server)(nil), "internal.Server") proto.RegisterType((*Server)(nil), "internal.Server")
proto.RegisterType((*Layout)(nil), "internal.Layout") proto.RegisterType((*Layout)(nil), "internal.Layout")
proto.RegisterType((*Cell)(nil), "internal.Cell") proto.RegisterType((*Cell)(nil), "internal.Cell")
@ -195,45 +232,49 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{ var fileDescriptorInternal = []byte{
// 636 bytes of a gzipped FileDescriptorProto // 693 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0x5f, 0x6e, 0xd3, 0x4e, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xdd, 0x6e, 0xd3, 0x4a,
0x10, 0xc7, 0xb5, 0xb1, 0x9d, 0xc4, 0xd3, 0x9f, 0xfa, 0x43, 0xab, 0x0a, 0x59, 0x88, 0x07, 0xcb, 0x10, 0xd6, 0xc6, 0x76, 0x12, 0x4f, 0x7a, 0x7a, 0x8e, 0x56, 0xd5, 0xc1, 0x42, 0x5c, 0x44, 0x16,
0x02, 0x29, 0x48, 0xa8, 0x0f, 0xf4, 0x04, 0x69, 0x5d, 0xa1, 0x40, 0x29, 0x65, 0xdb, 0x88, 0x27, 0x48, 0x41, 0x82, 0x5e, 0xb4, 0x4f, 0x90, 0xc6, 0x15, 0x0a, 0x94, 0x52, 0xb6, 0x8d, 0xb8, 0x02,
0x1e, 0xb6, 0xcd, 0xb4, 0xb5, 0xe4, 0xd8, 0x66, 0x6d, 0x93, 0xfa, 0x0e, 0x9c, 0x81, 0x43, 0xc0, 0x69, 0x9b, 0x6c, 0x9b, 0x48, 0x9b, 0xd8, 0xac, 0x6d, 0xd2, 0x3c, 0x02, 0x12, 0xcf, 0xc0, 0x43,
0x05, 0xb8, 0x03, 0x17, 0x42, 0x33, 0xbb, 0x4e, 0x82, 0xf8, 0xa3, 0xbe, 0xcd, 0x77, 0x66, 0x3c, 0xc0, 0x0b, 0xf0, 0x0e, 0xbc, 0x10, 0x9a, 0xd9, 0xb5, 0xe3, 0x8a, 0x1f, 0x55, 0xe2, 0x6e, 0xbe,
0xfe, 0xcc, 0x1f, 0x1b, 0x76, 0xb3, 0xa2, 0x41, 0x53, 0xe8, 0x7c, 0xbf, 0x32, 0x65, 0x53, 0xca, 0x99, 0xf1, 0xf8, 0x9b, 0xf9, 0x3e, 0x1b, 0x76, 0x17, 0xab, 0x42, 0x99, 0x95, 0xd4, 0xfb, 0x99,
0x71, 0xaf, 0x93, 0x6f, 0x02, 0x76, 0x8e, 0xef, 0xaa, 0xbc, 0x34, 0xba, 0xc9, 0xca, 0x42, 0xee, 0x49, 0x8b, 0x94, 0x77, 0x2b, 0x1c, 0x7f, 0x65, 0xd0, 0x3b, 0xbe, 0xc9, 0x74, 0x6a, 0x64, 0xb1,
0xc2, 0x60, 0x96, 0x46, 0x22, 0x16, 0x13, 0x4f, 0x0d, 0x66, 0xa9, 0x94, 0xe0, 0x9f, 0xea, 0x25, 0x48, 0x57, 0x7c, 0x17, 0x5a, 0xe3, 0x24, 0x62, 0x7d, 0x36, 0xf0, 0x44, 0x6b, 0x9c, 0x70, 0x0e,
0x46, 0x83, 0x58, 0x4c, 0x42, 0xc5, 0xb6, 0x7c, 0x08, 0xc3, 0x79, 0x8d, 0x66, 0x96, 0x46, 0x1e, 0xfe, 0xa9, 0x5c, 0xaa, 0xa8, 0xd5, 0x67, 0x83, 0x50, 0x50, 0xcc, 0xff, 0x87, 0xf6, 0x24, 0x57,
0xe7, 0x39, 0x45, 0xb9, 0xa9, 0x6e, 0x74, 0xe4, 0xdb, 0x5c, 0xb2, 0xe5, 0x63, 0x08, 0x8f, 0x0c, 0x66, 0x9c, 0x44, 0x1e, 0xf5, 0x39, 0x84, 0xbd, 0x89, 0x2c, 0x64, 0xe4, 0xdb, 0x5e, 0x8c, 0xf9,
0xea, 0x06, 0x17, 0xd3, 0x26, 0x0a, 0x38, 0x7d, 0xe3, 0xa0, 0xe8, 0xbc, 0x5a, 0xb8, 0xe8, 0xd0, 0x03, 0x08, 0x47, 0x46, 0xc9, 0x42, 0xcd, 0x86, 0x45, 0x14, 0x50, 0xfb, 0x36, 0x81, 0xd5, 0x49,
0x46, 0xd7, 0x0e, 0x19, 0xc1, 0x28, 0xc5, 0x6b, 0xdd, 0xe6, 0x4d, 0x34, 0x8a, 0xc5, 0x64, 0xac, 0x36, 0x73, 0xd5, 0xb6, 0xad, 0xd6, 0x09, 0x1e, 0x41, 0x27, 0x51, 0x57, 0xb2, 0xd4, 0x45, 0xd4,
0x7a, 0x99, 0x7c, 0x17, 0x30, 0x3c, 0x2f, 0x5b, 0x73, 0x85, 0xf7, 0x02, 0x96, 0xe0, 0x5f, 0x74, 0xe9, 0xb3, 0x41, 0x57, 0x54, 0x30, 0xfe, 0xc6, 0xa0, 0x7d, 0x9e, 0x96, 0x66, 0xaa, 0xee, 0x44,
0x15, 0x32, 0x6e, 0xa8, 0xd8, 0x96, 0x8f, 0x60, 0x4c, 0xd8, 0x05, 0xe5, 0x5a, 0xe0, 0xb5, 0xa6, 0x98, 0x83, 0x7f, 0xb1, 0xc9, 0x14, 0xd1, 0x0d, 0x05, 0xc5, 0xfc, 0x3e, 0x74, 0x91, 0xf6, 0x0a,
0xd8, 0x99, 0xae, 0xeb, 0x55, 0x69, 0x16, 0xcc, 0x1c, 0xaa, 0xb5, 0x96, 0x0f, 0xc0, 0x9b, 0xab, 0x7b, 0x2d, 0xe1, 0x1a, 0x63, 0xed, 0x4c, 0xe6, 0xf9, 0x3a, 0x35, 0x33, 0xe2, 0x1c, 0x8a, 0x1a,
0x13, 0x86, 0x0d, 0x15, 0x99, 0x7f, 0xc7, 0xa4, 0x3a, 0x17, 0x98, 0xe3, 0x8d, 0xd1, 0xd7, 0xd1, 0xf3, 0xff, 0xc0, 0x9b, 0x88, 0x13, 0x22, 0x1b, 0x0a, 0x0c, 0x7f, 0x4f, 0x13, 0xe7, 0x5c, 0x28,
0xd8, 0xd6, 0xe9, 0x75, 0xf2, 0x99, 0x5a, 0x40, 0xf3, 0x09, 0xcd, 0xbd, 0x5a, 0xd8, 0xc6, 0xf5, 0xad, 0xae, 0x8d, 0xbc, 0x8a, 0xba, 0x76, 0x4e, 0x85, 0xe3, 0x77, 0x10, 0x26, 0x32, 0x9f, 0x5f,
0xfe, 0x81, 0xeb, 0xff, 0x19, 0x37, 0xd8, 0xe0, 0xee, 0x41, 0x70, 0x6e, 0xae, 0x66, 0xa9, 0x9b, 0xa6, 0xd2, 0xcc, 0xee, 0xb4, 0xc4, 0x53, 0x08, 0xa6, 0x4a, 0xeb, 0x3c, 0xf2, 0xfa, 0xde, 0xa0,
0xb7, 0x15, 0xc9, 0x17, 0x01, 0xc3, 0x13, 0xdd, 0x95, 0x6d, 0xb3, 0x85, 0x13, 0x32, 0x4e, 0x0c, 0x77, 0x70, 0x6f, 0xbf, 0xd6, 0xb4, 0x9e, 0x33, 0x52, 0x5a, 0x0b, 0xdb, 0x15, 0x7f, 0x64, 0xf0,
0x3b, 0xd3, 0xaa, 0xca, 0xb3, 0x2b, 0xbe, 0x10, 0x47, 0xb5, 0xed, 0xa2, 0x8c, 0x37, 0xa8, 0xeb, 0xcf, 0xad, 0x02, 0xdf, 0x01, 0x76, 0x43, 0xef, 0x08, 0x04, 0xbb, 0x41, 0xb4, 0xa1, 0xf9, 0x81,
0xd6, 0xe0, 0x12, 0x8b, 0xc6, 0xf1, 0x6d, 0xbb, 0xe4, 0x13, 0x08, 0x8e, 0x30, 0xcf, 0xeb, 0xc8, 0x60, 0x1b, 0x44, 0x6b, 0x3a, 0x4f, 0x20, 0xd8, 0x1a, 0xd1, 0x9c, 0x8e, 0x12, 0x08, 0x36, 0xc7,
0x8f, 0xbd, 0xc9, 0xce, 0x8b, 0xdd, 0xfd, 0xf5, 0x41, 0x92, 0x5b, 0xd9, 0x20, 0x35, 0x32, 0x6d, 0xfd, 0xde, 0x97, 0xca, 0x2c, 0x54, 0x1e, 0x05, 0x7d, 0x6f, 0x10, 0x8a, 0x0a, 0x22, 0x4d, 0xba,
0x9b, 0xf2, 0x3a, 0x2f, 0x57, 0x4c, 0x3c, 0x56, 0x6b, 0x9d, 0xfc, 0x10, 0xe0, 0x53, 0x96, 0xfc, 0x9f, 0x3d, 0x06, 0xc5, 0x98, 0x2b, 0xf0, 0xd6, 0x1d, 0x9b, 0xc3, 0x38, 0xfe, 0x84, 0x72, 0x29,
0x0f, 0xc4, 0x1d, 0xd3, 0x05, 0x4a, 0xdc, 0x91, 0xea, 0x18, 0x29, 0x50, 0xa2, 0x23, 0xb5, 0xe2, 0xf3, 0x41, 0x99, 0x3b, 0x6d, 0xda, 0x94, 0xc6, 0xfb, 0x83, 0x34, 0xfe, 0xaf, 0xa5, 0x09, 0xb6,
0xd7, 0x07, 0x4a, 0xac, 0x48, 0xdd, 0xf2, 0x40, 0x02, 0x25, 0x6e, 0xe5, 0x33, 0x18, 0x7d, 0x6c, 0xd2, 0xec, 0x41, 0x70, 0x6e, 0xa6, 0xe3, 0xc4, 0x79, 0xcb, 0x82, 0xf8, 0x33, 0x83, 0xf6, 0x89,
0xd1, 0x64, 0x58, 0x47, 0x01, 0x43, 0xfc, 0xbf, 0x81, 0x78, 0xd7, 0xa2, 0xe9, 0x54, 0x1f, 0xa7, 0xdc, 0xa4, 0x65, 0xd1, 0xa0, 0x13, 0x12, 0x9d, 0x3e, 0xf4, 0x86, 0x59, 0xa6, 0x17, 0x53, 0xfa,
0x07, 0x33, 0xb7, 0x61, 0x91, 0xd1, 0x3a, 0x78, 0xec, 0x23, 0xbb, 0x0e, 0x1e, 0x79, 0x04, 0xa3, 0x1a, 0x1c, 0xab, 0x66, 0x0a, 0x3b, 0x5e, 0x2a, 0x99, 0x97, 0x46, 0x2d, 0xd5, 0xaa, 0x70, 0xfc,
0xce, 0xe8, 0xe2, 0x06, 0xeb, 0x68, 0x1c, 0x7b, 0x13, 0x4f, 0xf5, 0x92, 0x23, 0xb9, 0xbe, 0xc4, 0x9a, 0x29, 0xfe, 0x10, 0x82, 0x11, 0x09, 0xe5, 0x93, 0x50, 0xbb, 0x5b, 0xa1, 0xac, 0x3e, 0x54,
0xbc, 0x8e, 0xc2, 0xd8, 0x9b, 0x84, 0xaa, 0x97, 0x54, 0xa7, 0xa1, 0x2b, 0x04, 0x5b, 0x87, 0xec, 0xc4, 0x45, 0x86, 0x65, 0x91, 0x5e, 0xe9, 0x74, 0x4d, 0x8c, 0xbb, 0xa2, 0xc6, 0xf1, 0x77, 0x06,
0xe4, 0xab, 0x80, 0x80, 0x5f, 0x4e, 0xcf, 0x1d, 0x95, 0xcb, 0xa5, 0x2e, 0x16, 0x6e, 0xf4, 0xbd, 0xfe, 0x5f, 0x49, 0xf6, 0xf8, 0xb6, 0x64, 0xbd, 0x83, 0x7f, 0xb7, 0x24, 0x5e, 0x97, 0xca, 0x6c,
0xa4, 0x7d, 0xa4, 0x87, 0x6e, 0xec, 0x83, 0xf4, 0x90, 0xb4, 0x3a, 0x73, 0x43, 0x1e, 0xa8, 0x33, 0xb6, 0x1a, 0xee, 0x00, 0x5b, 0x38, 0x01, 0xd9, 0xa2, 0x56, 0xb4, 0xd3, 0x50, 0x34, 0x82, 0xce,
0x9a, 0xda, 0x4b, 0x53, 0xb6, 0xd5, 0x61, 0x67, 0xc7, 0x1b, 0xaa, 0xb5, 0xa6, 0x4f, 0xf5, 0xfd, 0xc6, 0xc8, 0xd5, 0xb5, 0xca, 0xa3, 0x6e, 0xdf, 0x1b, 0x78, 0xa2, 0x82, 0x54, 0xd1, 0xf2, 0x52,
0x2d, 0x1a, 0xd7, 0x73, 0xa8, 0x9c, 0xa2, 0x23, 0x38, 0x21, 0x2a, 0xd7, 0xa5, 0x15, 0xf2, 0x29, 0xe9, 0x3c, 0x0a, 0xad, 0x33, 0x1c, 0xac, 0x5d, 0x00, 0x0d, 0x17, 0x7c, 0x61, 0x10, 0xd0, 0xcb,
0x04, 0x8a, 0xba, 0xe0, 0x56, 0x7f, 0x19, 0x10, 0xbb, 0x95, 0x8d, 0x26, 0x07, 0x2e, 0x8d, 0xaa, 0xf1, 0xb9, 0x51, 0xba, 0x5c, 0xca, 0xd5, 0xcc, 0x9d, 0xbe, 0x82, 0xa8, 0x47, 0x72, 0xe4, 0xce,
0xcc, 0xab, 0x0a, 0x8d, 0xbb, 0x5d, 0x2b, 0xb8, 0x76, 0xb9, 0x42, 0xc3, 0xc8, 0x9e, 0xb2, 0x22, 0xde, 0x4a, 0x8e, 0x10, 0x8b, 0x33, 0x77, 0xe4, 0x96, 0x38, 0xc3, 0xab, 0x3d, 0x33, 0x69, 0x99,
0xf9, 0x00, 0xe1, 0x34, 0x47, 0xd3, 0xa8, 0x36, 0xc7, 0xdf, 0x4e, 0x4c, 0x82, 0xff, 0xea, 0xfc, 0x1d, 0x6d, 0xec, 0x79, 0x43, 0x51, 0x63, 0xfc, 0x2d, 0xbd, 0x99, 0x2b, 0x53, 0xdb, 0xd4, 0x21,
0xed, 0x69, 0x7f, 0xf1, 0x64, 0x6f, 0xee, 0xd4, 0xdb, 0xba, 0x53, 0x6a, 0xe8, 0xb5, 0xae, 0xf4, 0x34, 0xc1, 0x09, 0xb2, 0x72, 0x5b, 0x5a, 0xc0, 0x1f, 0x41, 0x20, 0x70, 0x0b, 0x5a, 0xf5, 0xd6,
0x2c, 0xe5, 0xc5, 0x7a, 0xca, 0xa9, 0xe4, 0x39, 0xf8, 0xf4, 0x3d, 0x6c, 0x55, 0xf6, 0xb9, 0xf2, 0x81, 0x28, 0x2d, 0x6c, 0x35, 0x3e, 0x74, 0x6d, 0x38, 0x65, 0x92, 0x65, 0xca, 0x38, 0xef, 0x5a,
0x1e, 0x04, 0xc7, 0x4b, 0x9d, 0xe5, 0xae, 0xb4, 0x15, 0x97, 0x43, 0xfe, 0x0d, 0x1e, 0xfc, 0x0c, 0x40, 0xb3, 0xd3, 0xb5, 0x32, 0x44, 0xd9, 0x13, 0x16, 0xc4, 0x6f, 0x21, 0x1c, 0x6a, 0x65, 0x0a,
0x00, 0x00, 0xff, 0xff, 0x92, 0x9e, 0x41, 0x68, 0x18, 0x05, 0x00, 0x00, 0x51, 0x6a, 0xf5, 0x93, 0xc5, 0x38, 0xf8, 0xcf, 0xcf, 0x5f, 0x9d, 0x56, 0x8e, 0xc7, 0x78, 0xeb,
0x53, 0xaf, 0xe1, 0x53, 0x5c, 0xe8, 0x85, 0xcc, 0xe4, 0x38, 0x21, 0x61, 0x3d, 0xe1, 0x50, 0xfc,
0x04, 0x7c, 0xfc, 0x1e, 0x1a, 0x93, 0x7d, 0x9a, 0xbc, 0x07, 0xc1, 0xf1, 0x52, 0x2e, 0xb4, 0x1b,
0x6d, 0xc1, 0x65, 0x9b, 0x7e, 0xf9, 0x87, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x31, 0x9b, 0xcb,
0xb7, 0x04, 0x06, 0x00, 0x00,
} }

View File

@ -22,6 +22,22 @@ message Source {
string Telegraf = 8; // Telegraf is the db telegraf is written to. By default it is "telegraf" string Telegraf = 8; // Telegraf is the db telegraf is written to. By default it is "telegraf"
} }
message Dashboard {
int64 ID = 1; // ID is the unique ID of the dashboard
string Name = 2; // Name is the user-defined name of the dashboard
repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard
}
message DashboardCell {
int32 x = 1; // X-coordinate of Cell in the Dashboard
int32 y = 2; // Y-coordinate of Cell in the Dashboard
int32 w = 3; // Width of Cell in the Dashboard
int32 h = 4; // Height of Cell in the Dashboard
repeated string queries = 5; // Time-series data queries for Dashboard
string name = 6; // User-facing name for this Dashboard
string type = 7; // Dashboard visualization type
}
message Server { message Server {
int64 ID = 1; // ID is the unique ID of the server int64 ID = 1; // ID is the unique ID of the server
string Name = 2; // Name is the user-defined name for the server string Name = 2; // Name is the user-defined name for the server

View File

@ -13,6 +13,7 @@ const (
ErrSourceNotFound = Error("source not found") ErrSourceNotFound = Error("source not found")
ErrServerNotFound = Error("server not found") ErrServerNotFound = Error("server not found")
ErrLayoutNotFound = Error("layout not found") ErrLayoutNotFound = Error("layout not found")
ErrDashboardNotFound = Error("dashboard not found")
ErrUserNotFound = Error("user not found") ErrUserNotFound = Error("user not found")
ErrLayoutInvalid = Error("layout is invalid") ErrLayoutInvalid = Error("layout is invalid")
ErrAlertNotFound = Error("alert not found") ErrAlertNotFound = Error("alert not found")
@ -223,6 +224,41 @@ type UsersStore interface {
FindByEmail(ctx context.Context, Email string) (*User, error) FindByEmail(ctx context.Context, Email string) (*User, error)
} }
// DashboardID is the dashboard ID
type DashboardID int
// Dashboard represents all visual and query data for a dashboard
type Dashboard struct {
ID DashboardID `json:"id"`
Cells []DashboardCell `json:"cells"`
Name string `json:"name"`
}
// DashboardCell holds visual and query information for a cell
type DashboardCell struct {
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []string `json:"queries"`
Type string `json:"type"`
}
// DashboardsStore is the storage and retrieval of dashboards
type DashboardsStore interface {
// All lists all dashboards from the DashboardStore
All(context.Context) ([]Dashboard, error)
// Create a new Dashboard in the DashboardStore
Add(context.Context, Dashboard) (Dashboard, error)
// Delete the Dashboard from the DashboardStore if `ID` exists.
Delete(context.Context, Dashboard) error
// Get retrieves a dashboard if `ID` exists.
Get(ctx context.Context, id DashboardID) (Dashboard, error)
// Update replaces the dashboard information
Update(context.Context, Dashboard) error
}
// ExplorationID is a unique ID for an Exploration. // ExplorationID is a unique ID for an Exploration.
type ExplorationID int type ExplorationID int
@ -260,7 +296,7 @@ type Cell struct {
I string `json:"i"` I string `json:"i"`
Name string `json:"name"` Name string `json:"name"`
Queries []Query `json:"queries"` Queries []Query `json:"queries"`
Type string `json:"type"` Type string `json:"type"`
} }
// Layout is a collection of Cells for visualization // Layout is a collection of Cells for visualization

175
server/dashboards.go Normal file
View File

@ -0,0 +1,175 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
)
type dashboardLinks struct {
Self string `json:"self"` // Self link mapping to this resource
}
type dashboardResponse struct {
chronograf.Dashboard
Links dashboardLinks `json:"links"`
}
type getDashboardsResponse struct {
Dashboards []dashboardResponse `json:"dashboards"`
}
func newDashboardResponse(d chronograf.Dashboard) dashboardResponse {
base := "/chronograf/v1/dashboards"
return dashboardResponse{
Dashboard: d,
Links: dashboardLinks{
Self: fmt.Sprintf("%s/%d", base, d.ID),
},
}
}
// Dashboards returns all dashboards within the store
func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
dashboards, err := s.DashboardsStore.All(ctx)
if err != nil {
Error(w, http.StatusInternalServerError, "Error loading dashboards", s.Logger)
return
}
res := getDashboardsResponse{
Dashboards: []dashboardResponse{},
}
for _, dashboard := range dashboards {
res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard))
}
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// DashboardID returns a single specified dashboard
func (s *Service) DashboardID(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
return
}
ctx := r.Context()
e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
if err != nil {
notFound(w, id, s.Logger)
return
}
res := newDashboardResponse(e)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// NewDashboard creates and returns a new dashboard object
func (s *Service) NewDashboard(w http.ResponseWriter, r *http.Request) {
var dashboard chronograf.Dashboard
if err := json.NewDecoder(r.Body).Decode(&dashboard); err != nil {
invalidJSON(w, s.Logger)
return
}
if err := ValidDashboardRequest(dashboard); err != nil {
invalidData(w, err, s.Logger)
return
}
var err error
if dashboard, err = s.DashboardsStore.Add(r.Context(), dashboard); err != nil {
msg := fmt.Errorf("Error storing dashboard %v: %v", dashboard, err)
unknownErrorWithMessage(w, msg, s.Logger)
return
}
res := newDashboardResponse(dashboard)
w.Header().Add("Location", res.Links.Self)
encodeJSON(w, http.StatusCreated, res, s.Logger)
}
// RemoveDashboard deletes a dashboard
func (s *Service) RemoveDashboard(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
return
}
ctx := r.Context()
e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
if err != nil {
notFound(w, id, s.Logger)
return
}
if err := s.DashboardsStore.Delete(ctx, e); err != nil {
unknownErrorWithMessage(w, err, s.Logger)
return
}
w.WriteHeader(http.StatusNoContent)
}
// UpdateDashboard replaces a dashboard
func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
idParam, err := strconv.Atoi(httprouter.GetParamFromContext(ctx, "id"))
if err != nil {
msg := fmt.Sprintf("Could not parse dashboard ID: %s", err)
Error(w, http.StatusInternalServerError, msg, s.Logger)
}
id := chronograf.DashboardID(idParam)
_, err = s.DashboardsStore.Get(ctx, id)
if err != nil {
Error(w, http.StatusNotFound, fmt.Sprintf("ID %s not found", id), s.Logger)
return
}
var req chronograf.Dashboard
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
invalidJSON(w, s.Logger)
return
}
req.ID = id
if err := ValidDashboardRequest(req); err != nil {
invalidData(w, err, s.Logger)
return
}
if err := s.DashboardsStore.Update(ctx, req); err != nil {
msg := fmt.Sprintf("Error updating dashboard ID %s: %v", id, err)
Error(w, http.StatusInternalServerError, msg, s.Logger)
return
}
res := newDashboardResponse(req)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// ValidDashboardRequest verifies that the dashboard cells have a query
func ValidDashboardRequest(d chronograf.Dashboard) error {
if len(d.Cells) == 0 {
return fmt.Errorf("cells are required")
}
for _, c := range d.Cells {
for _, q := range c.Queries {
if len(q) == 0 {
return fmt.Errorf("query required")
}
}
}
return nil
}

View File

@ -109,6 +109,14 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.PATCH("/chronograf/v1/users/:id/explorations/:eid", service.UpdateExploration) router.PATCH("/chronograf/v1/users/:id/explorations/:eid", service.UpdateExploration)
router.DELETE("/chronograf/v1/users/:id/explorations/:eid", service.RemoveExploration) router.DELETE("/chronograf/v1/users/:id/explorations/:eid", service.RemoveExploration)
// Dashboards
router.GET("/chronograf/v1/dashboards", service.Dashboards)
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)
/* Authentication */ /* Authentication */
if opts.UseAuth { if opts.UseAuth {
auth := AuthAPI(opts, router) auth := AuthAPI(opts, router)

View File

@ -7,21 +7,23 @@ import (
) )
type getRoutesResponse struct { type getRoutesResponse struct {
Layouts string `json:"layouts"` // Location of the layouts endpoint Layouts string `json:"layouts"` // Location of the layouts endpoint
Mappings string `json:"mappings"` // Location of the application mappings endpoint Mappings string `json:"mappings"` // Location of the application mappings endpoint
Sources string `json:"sources"` // Location of the sources endpoint Sources string `json:"sources"` // Location of the sources endpoint
Users string `json:"users"` // Location of the users endpoint Users string `json:"users"` // Location of the users endpoint
Me string `json:"me"` // Location of the me endpoint Me string `json:"me"` // Location of the me endpoint
Dashboards string `json:"dashboards"` // Location of the dashboards endpoint
} }
// AllRoutes returns all top level routes within chronograf // AllRoutes returns all top level routes within chronograf
func AllRoutes(logger chronograf.Logger) http.HandlerFunc { func AllRoutes(logger chronograf.Logger) http.HandlerFunc {
routes := getRoutesResponse{ routes := getRoutesResponse{
Sources: "/chronograf/v1/sources", Sources: "/chronograf/v1/sources",
Layouts: "/chronograf/v1/layouts", Layouts: "/chronograf/v1/layouts",
Users: "/chronograf/v1/users", Users: "/chronograf/v1/users",
Me: "/chronograf/v1/me", Me: "/chronograf/v1/me",
Mappings: "/chronograf/v1/mappings", Mappings: "/chronograf/v1/mappings",
Dashboards: "/chronograf/v1/dashboards",
} }
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -81,7 +81,7 @@ func (s *Server) Serve() error {
httpServer := &graceful.Server{Server: new(http.Server)} httpServer := &graceful.Server{Server: new(http.Server)}
httpServer.SetKeepAlivesEnabled(true) httpServer.SetKeepAlivesEnabled(true)
httpServer.TCPKeepAlive = 1 * time.Minute httpServer.TCPKeepAlive = 5 * time.Second
httpServer.Handler = s.handler httpServer.Handler = s.handler
if !s.ReportingDisabled { if !s.ReportingDisabled {
@ -142,6 +142,7 @@ func openService(boltPath, cannedPath string, logger chronograf.Logger, useAuth
Logger: logger, Logger: logger,
}, },
LayoutStore: layouts, LayoutStore: layouts,
DashboardsStore: db.DashboardsStore,
AlertRulesStore: db.AlertsStore, AlertRulesStore: db.AlertsStore,
Logger: logger, Logger: logger,
UseAuth: useAuth, UseAuth: useAuth,

View File

@ -10,6 +10,7 @@ type Service struct {
LayoutStore chronograf.LayoutStore LayoutStore chronograf.LayoutStore
AlertRulesStore chronograf.AlertRulesStore AlertRulesStore chronograf.AlertRulesStore
UsersStore chronograf.UsersStore UsersStore chronograf.UsersStore
DashboardsStore chronograf.DashboardsStore
TimeSeries chronograf.TimeSeries TimeSeries chronograf.TimeSeries
Logger chronograf.Logger Logger chronograf.Logger
UseAuth bool UseAuth bool

View File

@ -1574,7 +1574,180 @@
} }
} }
} }
} },
"/dashboards": {
"get": {
"tags": [
"dashboards"
],
"summary": "List of all dashboards",
"responses": {
"200": {
"description": "An array of dashboards",
"schema": {
"$ref": "#/definitions/Dashboards"
}
},
"default": {
"description": "Unexpected internal service error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"post": {
"tags": [
"dashboards"
],
"summary": "Create new dashboard",
"parameters": [
{
"name": "dashboard",
"in": "body",
"description": "Configuration options for new dashboard",
"schema": {
"$ref": "#/definitions/Dashboard"
}
}
],
"responses": {
"201": {
"description": "Successfully created new dashboard",
"headers": {
"Location": {
"type": "string",
"format": "url",
"description": "Location of the newly created dashboard resource."
}
},
"schema": {
"$ref": "#/definitions/Dashboard"
}
},
"default": {
"description": "A processing or an unexpected error.",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/dashboards/{id}": {
"get": {
"tags": [
"dashboards"
],
"parameters": [
{
"name": "id",
"in": "path",
"type": "integer",
"description": "ID of the dashboard",
"required": true
}
],
"summary": "Specific dashboard",
"description": "Dashboards contain visual display information as well as links to queries",
"responses": {
"200": {
"description": "Returns the specified dashboard with links to queries.",
"schema": {
"$ref": "#/definitions/Dashboard"
}
},
"404": {
"description": "Unknown dashboard id",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal service error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"delete": {
"tags": [
"dashboards"
],
"parameters": [
{
"name": "id",
"in": "path",
"type": "integer",
"description": "ID of the layout",
"required": true
}
],
"summary": "Deletes the specified dashboard",
"responses": {
"204": {
"description": "Dashboard has been removed."
},
"404": {
"description": "Unknown dashboard id",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal service error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"put": {
"tags": [
"layouts"
],
"summary": "Replace dashboard information.",
"parameters": [
{
"name": "id",
"in": "path",
"type": "integer",
"description": "ID of a dashboard",
"required": true
},
{
"name": "config",
"in": "body",
"description": "dashboard configuration update parameters",
"schema": {
"$ref": "#/definitions/Dashboard"
},
"required": true
}
],
"responses": {
"200": {
"description": "Dashboard has been replaced and the new dashboard is returned.",
"schema": {
"$ref": "#/definitions/Dashboard"
}
},
"404": {
"description": "Happens when trying to access a non-existent dashboard.",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "A processing or an unexpected error.",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
}, },
"definitions": { "definitions": {
"Kapacitors": { "Kapacitors": {
@ -2100,6 +2273,94 @@
} }
} }
}, },
"Dashboards": {
"description": "a list of dashboards",
"type": "object",
"properties": {
"dashboards": {
"type": "array",
"items": {
"$ref": "#/definitions/Dashboard"
}
}
}
},
"Dashboard": {
"type": "object",
"properties": {
"id": {
"description": "the unique dashboard id",
"type": "integer",
"format": "int64"
},
"cells": {
"description": "a list of dashboard visualizations",
"type": "array",
"items": {
"description": "cell visualization information",
"type": "object",
"required": [
"queries"
],
"properties": {
"x": {
"description": "X-coordinate of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"y": {
"description": "Y-coordinate of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"w": {
"description": "Width of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"h": {
"description": "Height of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"queries": {
"description": "Time-series data queries for Cell.",
"type": "array",
"items": {
"description": "links to the queries to visualize",
"type": "string",
"format": "url"
}
},
"type": {
"description": "Cell visualization type",
"type": "string",
"enum": [
"single-stat",
"line",
"line-plus-single-stat"
],
"default": "line"
}
}
}
},
"name": {
"description": "the user-facing name of the dashboard",
"type": "string"
},
"links": {
"type": "object",
"properties": {
"self": {
"type": "string",
"description": "Self link mapping to this resource",
"format": "url"
}
}
}
}
},
"Routes": { "Routes": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2127,6 +2388,11 @@
"description": "Location of the application mappings endpoint", "description": "Location of the application mappings endpoint",
"type": "string", "type": "string",
"format": "url" "format": "url"
},
"dashboards": {
"description": "location of the dashboards endpoint",
"type": "string",
"format": "url"
} }
} }
}, },