Merge branch 'master' into moar-colors-n-stuff

pull/3060/head
Alex P 2018-03-19 18:23:53 -07:00
commit a98cc90fa3
515 changed files with 19128 additions and 23072 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.4.1.3
current_version = 1.4.2.3
files = README.md server/swagger.json
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+)
serialize = {major}.{minor}.{patch}.{release}

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ chronograf*.db
*_gen.go
canned/apps_gen.go
npm-debug.log
yarn-error.log

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ We really like to receive feature requests, as it helps us prioritize our work.
Contributing to the source code
-------------------------------
Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses Yarn for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
Chronograf is built using Go for its API backend and serving the front-end assets, and uses Dep for dependency management. The front-end visualization is built with React (JavaScript) and uses Yarn for dependency management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
Submitting a pull request
-------------------------
@ -43,9 +43,13 @@ Signing the CLA
If you are going to be contributing back to Chronograf please take a second to sign our CLA, which can be found
[on our website](https://influxdata.com/community/cla/).
Installing Yarn
Installing & Using Yarn
--------------
You'll need to install Yarn to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the Yarn installation page](https://yarnpkg.com/en/docs/install).
You'll need to install Yarn to manage the frontend (JavaScript) dependencies.
* [Install Yarn](https://yarnpkg.com/en/docs/install)
To add a dependency via Yarn, for example, run `yarn add <dependency>` from within the `/chronograf/ui` directory.
Installing Go
-------------
@ -62,13 +66,13 @@ running the following:
gvm use go1.7.5 --default
```
Installing GDM
Installing & Using Dep
--------------
Chronograf uses [gdm](https://github.com/sparrc/gdm) to manage dependencies. Install it by running the following:
You'll need to install Dep to manage the backend (Go) dependencies.
```bash
go get github.com/sparrc/gdm
```
* [Install Dep](https://github.com/golang/dep)
To add a dependency via Dep, for example, run `dep ensure -add <dependency>` from within the `/chronograf` directory. _Note that as of this writing, `dep ensure` will modify many extraneous vendor files, so you'll need to run `dep prune` to clean this up before committing your changes. Apparently, the next version of `dep` will take care of this step for you._
Revision Control Systems
------------------------

89
Gopkg.lock generated
View File

@ -39,34 +39,7 @@
[[projects]]
name = "github.com/gogo/protobuf"
packages = [
"gogoproto",
"jsonpb",
"plugin/compare",
"plugin/defaultcheck",
"plugin/description",
"plugin/embedcheck",
"plugin/enumstringer",
"plugin/equal",
"plugin/face",
"plugin/gostring",
"plugin/marshalto",
"plugin/oneofcheck",
"plugin/populate",
"plugin/size",
"plugin/stringer",
"plugin/testgen",
"plugin/union",
"plugin/unmarshal",
"proto",
"protoc-gen-gogo",
"protoc-gen-gogo/descriptor",
"protoc-gen-gogo/generator",
"protoc-gen-gogo/grpc",
"protoc-gen-gogo/plugin",
"vanity",
"vanity/command"
]
packages = ["gogoproto","jsonpb","plugin/compare","plugin/defaultcheck","plugin/description","plugin/embedcheck","plugin/enumstringer","plugin/equal","plugin/face","plugin/gostring","plugin/marshalto","plugin/oneofcheck","plugin/populate","plugin/size","plugin/stringer","plugin/testgen","plugin/union","plugin/unmarshal","proto","protoc-gen-gogo","protoc-gen-gogo/descriptor","protoc-gen-gogo/generator","protoc-gen-gogo/grpc","protoc-gen-gogo/plugin","vanity","vanity/command"]
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
[[projects]]
@ -77,13 +50,7 @@
[[projects]]
name = "github.com/google/go-cmp"
packages = [
"cmp",
"cmp/cmpopts",
"cmp/internal/diff",
"cmp/internal/function",
"cmp/internal/value"
]
packages = ["cmp","cmp/cmpopts","cmp/internal/diff","cmp/internal/function","cmp/internal/value"]
revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97"
version = "v0.1.0"
@ -100,28 +67,13 @@
[[projects]]
name = "github.com/influxdata/influxdb"
packages = [
"influxql",
"influxql/internal",
"influxql/neldermead",
"models",
"pkg/escape"
]
packages = ["influxql","influxql/internal","influxql/neldermead","models","pkg/escape"]
revision = "cd9363b52cac452113b95554d98a6be51beda24e"
version = "v1.1.5"
[[projects]]
name = "github.com/influxdata/kapacitor"
packages = [
"client/v1",
"pipeline",
"pipeline/tick",
"services/k8s/client",
"tick",
"tick/ast",
"tick/stateful",
"udf/agent"
]
packages = ["client/v1","pipeline","pipeline/tick","services/k8s/client","tick","tick/ast","tick/stateful","udf/agent"]
revision = "6de30070b39afde111fea5e041281126fe8aae31"
[[projects]]
@ -163,21 +115,13 @@
[[projects]]
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp"
]
packages = ["context","context/ctxhttp"]
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
[[projects]]
name = "golang.org/x/oauth2"
packages = [
".",
"github",
"heroku",
"internal"
]
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
packages = [".","github","heroku","internal"]
revision = "2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0"
[[projects]]
branch = "master"
@ -187,31 +131,18 @@
[[projects]]
name = "google.golang.org/api"
packages = [
"gensupport",
"googleapi",
"googleapi/internal/uritemplates",
"oauth2/v2"
]
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
[[projects]]
name = "google.golang.org/appengine"
packages = [
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "11df631364d11bc05c8f71af1aa735360b5a40a793d32d47d1f1d8c694a55f6f"
inputs-digest = "a4df1b0953349e64a89581f4b83ac3a2f40e17681e19f8de3cbf828b6375a3ba"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -62,7 +62,7 @@ required = ["github.com/kevinburke/go-bindata","github.com/gogo/protobuf/proto",
[[constraint]]
name = "golang.org/x/oauth2"
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
revision = "2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0"
[[constraint]]
name = "google.golang.org/api"

View File

@ -101,7 +101,7 @@ gotestrace:
go test -race ./...
jstest:
cd ui && yarn test
cd ui && yarn test --runInBand
run: ${BINARY}
./chronograf

View File

@ -136,7 +136,7 @@ option.
## Versions
The most recent version of Chronograf is
[v1.4.1.3](https://www.influxdata.com/downloads/).
[v1.4.2.3](https://www.influxdata.com/downloads/).
Spotted a bug or have a feature request? Please open
[an issue](https://github.com/influxdata/chronograf/issues/new)!
@ -178,7 +178,7 @@ By default, chronograf runs on port `8888`.
To get started right away with Docker, you can pull down our latest release:
```sh
docker pull chronograf:1.4.1.3
docker pull chronograf:1.4.2.3
```
### From Source

View File

@ -75,14 +75,15 @@ func UnmarshalSource(data []byte, s *chronograf.Source) error {
// MarshalServer encodes a server to binary protobuf format.
func MarshalServer(s chronograf.Server) ([]byte, error) {
return proto.Marshal(&Server{
ID: int64(s.ID),
SrcID: int64(s.SrcID),
Name: s.Name,
Username: s.Username,
Password: s.Password,
URL: s.URL,
Active: s.Active,
Organization: s.Organization,
ID: int64(s.ID),
SrcID: int64(s.SrcID),
Name: s.Name,
Username: s.Username,
Password: s.Password,
URL: s.URL,
Active: s.Active,
Organization: s.Organization,
InsecureSkipVerify: s.InsecureSkipVerify,
})
}
@ -101,6 +102,7 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error {
s.URL = pb.URL
s.Active = pb.Active
s.Organization = pb.Organization
s.InsecureSkipVerify = pb.InsecureSkipVerify
return nil
}
@ -263,6 +265,27 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
}
}
sortBy := &TableColumn{
InternalName: c.TableOptions.SortBy.InternalName,
DisplayName: c.TableOptions.SortBy.DisplayName,
}
columnNames := make([]*TableColumn, len(c.TableOptions.ColumnNames))
for i, column := range c.TableOptions.ColumnNames {
columnNames[i] = &TableColumn{
InternalName: column.InternalName,
DisplayName: column.DisplayName,
}
}
tableOptions := &TableOptions{
TimeFormat: c.TableOptions.TimeFormat,
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
SortBy: sortBy,
Wrapping: c.TableOptions.Wrapping,
ColumnNames: columnNames,
}
cells[i] = &DashboardCell{
ID: c.ID,
X: c.X,
@ -278,6 +301,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Type: c.Legend.Type,
Orientation: c.Legend.Orientation,
},
TableOptions: tableOptions,
}
}
templates := make([]*Template, len(d.Templates))
@ -404,18 +428,48 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
legend.Orientation = c.Legend.Orientation
}
tableOptions := chronograf.TableOptions{}
if c.TableOptions != nil {
sortBy := chronograf.TableColumn{}
if c.TableOptions.SortBy != nil {
sortBy.InternalName = c.TableOptions.SortBy.InternalName
sortBy.DisplayName = c.TableOptions.SortBy.DisplayName
}
tableOptions.SortBy = sortBy
columnNames := make([]chronograf.TableColumn, len(c.TableOptions.ColumnNames))
for i, column := range c.TableOptions.ColumnNames {
columnNames[i] = chronograf.TableColumn{}
columnNames[i].InternalName = column.InternalName
columnNames[i].DisplayName = column.DisplayName
}
tableOptions.ColumnNames = columnNames
tableOptions.TimeFormat = c.TableOptions.TimeFormat
tableOptions.VerticalTimeAxis = c.TableOptions.VerticalTimeAxis
tableOptions.Wrapping = c.TableOptions.Wrapping
}
// FIXME: this is merely for legacy cells and
// should be removed as soon as possible
cellType := c.Type
if cellType == "" {
cellType = "line"
}
cells[i] = chronograf.DashboardCell{
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: c.Type,
Axes: axes,
CellColors: colors,
Legend: legend,
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: cellType,
Axes: axes,
CellColors: colors,
Legend: legend,
TableOptions: tableOptions,
}
}

View File

@ -11,6 +11,8 @@ It has these top-level messages:
Source
Dashboard
DashboardCell
TableOptions
TableColumn
Color
Legend
Axis
@ -210,17 +212,18 @@ func (m *Dashboard) GetOrganization() string {
}
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 []*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"`
ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"`
Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`
Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"`
Legend *Legend `protobuf:"bytes,11,opt,name=legend" json:"legend,omitempty"`
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 []*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"`
ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"`
Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`
Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"`
Legend *Legend `protobuf:"bytes,11,opt,name=legend" json:"legend,omitempty"`
TableOptions *TableOptions `protobuf:"bytes,12,opt,name=tableOptions" json:"tableOptions,omitempty"`
}
func (m *DashboardCell) Reset() { *m = DashboardCell{} }
@ -305,6 +308,85 @@ func (m *DashboardCell) GetLegend() *Legend {
return nil
}
func (m *DashboardCell) GetTableOptions() *TableOptions {
if m != nil {
return m.TableOptions
}
return nil
}
type TableOptions struct {
TimeFormat string `protobuf:"bytes,1,opt,name=timeFormat,proto3" json:"timeFormat,omitempty"`
VerticalTimeAxis bool `protobuf:"varint,2,opt,name=verticalTimeAxis,proto3" json:"verticalTimeAxis,omitempty"`
SortBy *TableColumn `protobuf:"bytes,3,opt,name=sortBy" json:"sortBy,omitempty"`
Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"`
ColumnNames []*TableColumn `protobuf:"bytes,5,rep,name=columnNames" json:"columnNames,omitempty"`
}
func (m *TableOptions) Reset() { *m = TableOptions{} }
func (m *TableOptions) String() string { return proto.CompactTextString(m) }
func (*TableOptions) ProtoMessage() {}
func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (m *TableOptions) GetTimeFormat() string {
if m != nil {
return m.TimeFormat
}
return ""
}
func (m *TableOptions) GetVerticalTimeAxis() bool {
if m != nil {
return m.VerticalTimeAxis
}
return false
}
func (m *TableOptions) GetSortBy() *TableColumn {
if m != nil {
return m.SortBy
}
return nil
}
func (m *TableOptions) GetWrapping() string {
if m != nil {
return m.Wrapping
}
return ""
}
func (m *TableOptions) GetColumnNames() []*TableColumn {
if m != nil {
return m.ColumnNames
}
return nil
}
type TableColumn struct {
InternalName string `protobuf:"bytes,1,opt,name=internalName,proto3" json:"internalName,omitempty"`
DisplayName string `protobuf:"bytes,2,opt,name=displayName,proto3" json:"displayName,omitempty"`
}
func (m *TableColumn) Reset() { *m = TableColumn{} }
func (m *TableColumn) String() string { return proto.CompactTextString(m) }
func (*TableColumn) ProtoMessage() {}
func (*TableColumn) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (m *TableColumn) GetInternalName() string {
if m != nil {
return m.InternalName
}
return ""
}
func (m *TableColumn) GetDisplayName() string {
if m != nil {
return m.DisplayName
}
return ""
}
type Color struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
@ -316,7 +398,7 @@ type Color struct {
func (m *Color) Reset() { *m = Color{} }
func (m *Color) String() string { return proto.CompactTextString(m) }
func (*Color) ProtoMessage() {}
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (m *Color) GetID() string {
if m != nil {
@ -361,7 +443,7 @@ type Legend struct {
func (m *Legend) Reset() { *m = Legend{} }
func (m *Legend) String() string { return proto.CompactTextString(m) }
func (*Legend) ProtoMessage() {}
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (m *Legend) GetType() string {
if m != nil {
@ -390,7 +472,7 @@ type Axis struct {
func (m *Axis) Reset() { *m = Axis{} }
func (m *Axis) String() string { return proto.CompactTextString(m) }
func (*Axis) ProtoMessage() {}
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
func (m *Axis) GetLegacyBounds() []int64 {
if m != nil {
@ -453,7 +535,7 @@ type Template struct {
func (m *Template) Reset() { *m = Template{} }
func (m *Template) String() string { return proto.CompactTextString(m) }
func (*Template) ProtoMessage() {}
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
func (m *Template) GetID() string {
if m != nil {
@ -506,7 +588,7 @@ type TemplateValue struct {
func (m *TemplateValue) Reset() { *m = TemplateValue{} }
func (m *TemplateValue) String() string { return proto.CompactTextString(m) }
func (*TemplateValue) ProtoMessage() {}
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
func (m *TemplateValue) GetType() string {
if m != nil {
@ -541,7 +623,7 @@ type TemplateQuery struct {
func (m *TemplateQuery) Reset() { *m = TemplateQuery{} }
func (m *TemplateQuery) String() string { return proto.CompactTextString(m) }
func (*TemplateQuery) ProtoMessage() {}
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func (m *TemplateQuery) GetCommand() string {
if m != nil {
@ -586,20 +668,21 @@ func (m *TemplateQuery) GetFieldKey() string {
}
type Server 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"`
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"`
Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"`
Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,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"`
Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"`
Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"`
InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"`
}
func (m *Server) Reset() { *m = Server{} }
func (m *Server) String() string { return proto.CompactTextString(m) }
func (*Server) ProtoMessage() {}
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
func (m *Server) GetID() int64 {
if m != nil {
@ -657,6 +740,13 @@ func (m *Server) GetOrganization() string {
return ""
}
func (m *Server) GetInsecureSkipVerify() bool {
if m != nil {
return m.InsecureSkipVerify
}
return false
}
type Layout struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
@ -668,7 +758,7 @@ type Layout struct {
func (m *Layout) Reset() { *m = Layout{} }
func (m *Layout) String() string { return proto.CompactTextString(m) }
func (*Layout) ProtoMessage() {}
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
func (m *Layout) GetID() string {
if m != nil {
@ -722,7 +812,7 @@ type Cell struct {
func (m *Cell) Reset() { *m = Cell{} }
func (m *Cell) String() string { return proto.CompactTextString(m) }
func (*Cell) ProtoMessage() {}
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
func (m *Cell) GetX() int32 {
if m != nil {
@ -816,7 +906,7 @@ type Query struct {
func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
func (m *Query) GetCommand() string {
if m != nil {
@ -890,7 +980,7 @@ type TimeShift struct {
func (m *TimeShift) Reset() { *m = TimeShift{} }
func (m *TimeShift) String() string { return proto.CompactTextString(m) }
func (*TimeShift) ProtoMessage() {}
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (m *TimeShift) GetLabel() string {
if m != nil {
@ -921,7 +1011,7 @@ type Range struct {
func (m *Range) Reset() { *m = Range{} }
func (m *Range) String() string { return proto.CompactTextString(m) }
func (*Range) ProtoMessage() {}
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
func (m *Range) GetUpper() int64 {
if m != nil {
@ -947,7 +1037,7 @@ type AlertRule struct {
func (m *AlertRule) Reset() { *m = AlertRule{} }
func (m *AlertRule) String() string { return proto.CompactTextString(m) }
func (*AlertRule) ProtoMessage() {}
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
func (m *AlertRule) GetID() string {
if m != nil {
@ -989,7 +1079,7 @@ type User struct {
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
func (m *User) GetID() uint64 {
if m != nil {
@ -1041,7 +1131,7 @@ type Role struct {
func (m *Role) Reset() { *m = Role{} }
func (m *Role) String() string { return proto.CompactTextString(m) }
func (*Role) ProtoMessage() {}
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} }
func (m *Role) GetOrganization() string {
if m != nil {
@ -1068,7 +1158,7 @@ type Mapping struct {
func (m *Mapping) Reset() { *m = Mapping{} }
func (m *Mapping) String() string { return proto.CompactTextString(m) }
func (*Mapping) ProtoMessage() {}
func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
func (m *Mapping) GetProvider() string {
if m != nil {
@ -1114,7 +1204,7 @@ type Organization struct {
func (m *Organization) Reset() { *m = Organization{} }
func (m *Organization) String() string { return proto.CompactTextString(m) }
func (*Organization) ProtoMessage() {}
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} }
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
func (m *Organization) GetID() string {
if m != nil {
@ -1144,7 +1234,7 @@ type Config struct {
func (m *Config) Reset() { *m = Config{} }
func (m *Config) String() string { return proto.CompactTextString(m) }
func (*Config) ProtoMessage() {}
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{22} }
func (m *Config) GetAuth() *AuthConfig {
if m != nil {
@ -1160,7 +1250,7 @@ type AuthConfig struct {
func (m *AuthConfig) Reset() { *m = AuthConfig{} }
func (m *AuthConfig) String() string { return proto.CompactTextString(m) }
func (*AuthConfig) ProtoMessage() {}
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{23} }
func (m *AuthConfig) GetSuperAdminNewUsers() bool {
if m != nil {
@ -1177,7 +1267,7 @@ type BuildInfo struct {
func (m *BuildInfo) Reset() { *m = BuildInfo{} }
func (m *BuildInfo) String() string { return proto.CompactTextString(m) }
func (*BuildInfo) ProtoMessage() {}
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{22} }
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{24} }
func (m *BuildInfo) GetVersion() string {
if m != nil {
@ -1197,6 +1287,8 @@ func init() {
proto.RegisterType((*Source)(nil), "internal.Source")
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
proto.RegisterType((*TableOptions)(nil), "internal.TableOptions")
proto.RegisterType((*TableColumn)(nil), "internal.TableColumn")
proto.RegisterType((*Color)(nil), "internal.Color")
proto.RegisterType((*Legend)(nil), "internal.Legend")
proto.RegisterType((*Axis)(nil), "internal.Axis")
@ -1222,93 +1314,103 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 1406 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
0x10, 0x97, 0x63, 0x3b, 0x89, 0x27, 0xd7, 0x52, 0x99, 0x13, 0x35, 0x45, 0x42, 0xc1, 0x02, 0x11,
0x04, 0x3d, 0xd0, 0x55, 0x48, 0x08, 0x41, 0xa5, 0xdc, 0x05, 0x95, 0xa3, 0xd7, 0xf6, 0xba, 0xb9,
0x3b, 0x9e, 0x50, 0xb5, 0x97, 0x4c, 0x12, 0xab, 0x8e, 0x6d, 0xd6, 0xf6, 0x5d, 0xcc, 0x87, 0x41,
0x42, 0x82, 0x2f, 0x80, 0x78, 0xe7, 0x15, 0xf1, 0x41, 0xf8, 0x0a, 0x3c, 0x21, 0xa1, 0xd9, 0x5d,
0xff, 0xc9, 0x25, 0xad, 0xfa, 0x80, 0x78, 0xdb, 0xdf, 0xcc, 0x66, 0x76, 0xfe, 0xfc, 0x66, 0xc6,
0x81, 0x9b, 0x41, 0x94, 0xa1, 0x88, 0x78, 0xb8, 0x97, 0x88, 0x38, 0x8b, 0xdd, 0x6e, 0x89, 0xfd,
0xbf, 0x5a, 0xd0, 0x1e, 0xc7, 0xb9, 0x98, 0xa0, 0x7b, 0x13, 0x5a, 0x47, 0x23, 0xcf, 0xe8, 0x1b,
0x03, 0x93, 0xb5, 0x8e, 0x46, 0xae, 0x0b, 0xd6, 0x63, 0xbe, 0x44, 0xaf, 0xd5, 0x37, 0x06, 0x0e,
0x93, 0x67, 0x92, 0x9d, 0x16, 0x09, 0x7a, 0xa6, 0x92, 0xd1, 0xd9, 0xbd, 0x03, 0xdd, 0xb3, 0x94,
0xac, 0x2d, 0xd1, 0xb3, 0xa4, 0xbc, 0xc2, 0xa4, 0x3b, 0xe1, 0x69, 0x7a, 0x15, 0x8b, 0xa9, 0x67,
0x2b, 0x5d, 0x89, 0xdd, 0x5b, 0x60, 0x9e, 0xb1, 0x63, 0xaf, 0x2d, 0xc5, 0x74, 0x74, 0x3d, 0xe8,
0x8c, 0x70, 0xc6, 0xf3, 0x30, 0xf3, 0x3a, 0x7d, 0x63, 0xd0, 0x65, 0x25, 0x24, 0x3b, 0xa7, 0x18,
0xe2, 0x5c, 0xf0, 0x99, 0xd7, 0x55, 0x76, 0x4a, 0xec, 0xee, 0x81, 0x7b, 0x14, 0xa5, 0x38, 0xc9,
0x05, 0x8e, 0x9f, 0x07, 0xc9, 0x39, 0x8a, 0x60, 0x56, 0x78, 0x8e, 0x34, 0xb0, 0x45, 0x43, 0xaf,
0x3c, 0xc2, 0x8c, 0xd3, 0xdb, 0x20, 0x4d, 0x95, 0xd0, 0xf5, 0x61, 0x67, 0xbc, 0xe0, 0x02, 0xa7,
0x63, 0x9c, 0x08, 0xcc, 0xbc, 0x9e, 0x54, 0xaf, 0xc9, 0xe8, 0xce, 0x13, 0x31, 0xe7, 0x51, 0xf0,
0x03, 0xcf, 0x82, 0x38, 0xf2, 0x76, 0xd4, 0x9d, 0xa6, 0x8c, 0xb2, 0xc4, 0xe2, 0x10, 0xbd, 0x1b,
0x2a, 0x4b, 0x74, 0xf6, 0x7f, 0x33, 0xc0, 0x19, 0xf1, 0x74, 0x71, 0x11, 0x73, 0x31, 0x7d, 0xa5,
0x5c, 0xdf, 0x05, 0x7b, 0x82, 0x61, 0x98, 0x7a, 0x66, 0xdf, 0x1c, 0xf4, 0xf6, 0x6f, 0xef, 0x55,
0x45, 0xac, 0xec, 0x1c, 0x62, 0x18, 0x32, 0x75, 0xcb, 0xfd, 0x04, 0x9c, 0x0c, 0x97, 0x49, 0xc8,
0x33, 0x4c, 0x3d, 0x4b, 0xfe, 0xc4, 0xad, 0x7f, 0x72, 0xaa, 0x55, 0xac, 0xbe, 0xb4, 0x11, 0x8a,
0xbd, 0x19, 0x8a, 0xff, 0x4f, 0x0b, 0x6e, 0xac, 0x3d, 0xe7, 0xee, 0x80, 0xb1, 0x92, 0x9e, 0xdb,
0xcc, 0x58, 0x11, 0x2a, 0xa4, 0xd7, 0x36, 0x33, 0x0a, 0x42, 0x57, 0x92, 0x1b, 0x36, 0x33, 0xae,
0x08, 0x2d, 0x24, 0x23, 0x6c, 0x66, 0x2c, 0xdc, 0x0f, 0xa0, 0xf3, 0x7d, 0x8e, 0x22, 0xc0, 0xd4,
0xb3, 0xa5, 0x77, 0xaf, 0xd5, 0xde, 0x3d, 0xcd, 0x51, 0x14, 0xac, 0xd4, 0x53, 0x36, 0x24, 0x9b,
0x14, 0x35, 0xe4, 0x99, 0x64, 0x19, 0x31, 0xaf, 0xa3, 0x64, 0x74, 0xd6, 0x59, 0x54, 0x7c, 0xa0,
0x2c, 0x7e, 0x0a, 0x16, 0x5f, 0x61, 0xea, 0x39, 0xd2, 0xfe, 0x3b, 0x2f, 0x48, 0xd8, 0xde, 0x70,
0x85, 0xe9, 0x57, 0x51, 0x26, 0x0a, 0x26, 0xaf, 0xbb, 0xef, 0x43, 0x7b, 0x12, 0x87, 0xb1, 0x48,
0x3d, 0xb8, 0xee, 0xd8, 0x21, 0xc9, 0x99, 0x56, 0xbb, 0x03, 0x68, 0x87, 0x38, 0xc7, 0x68, 0x2a,
0x99, 0xd1, 0xdb, 0xbf, 0x55, 0x5f, 0x3c, 0x96, 0x72, 0xa6, 0xf5, 0x77, 0x1e, 0x80, 0x53, 0xbd,
0x42, 0x44, 0x7f, 0x8e, 0x85, 0xcc, 0x99, 0xc3, 0xe8, 0xe8, 0xbe, 0x0b, 0xf6, 0x25, 0x0f, 0x73,
0x55, 0xef, 0xde, 0xfe, 0xcd, 0xda, 0xce, 0x70, 0x15, 0xa4, 0x4c, 0x29, 0x3f, 0x6f, 0x7d, 0x66,
0xf8, 0x73, 0xb0, 0xa5, 0x0f, 0x0d, 0xc6, 0x38, 0x25, 0x63, 0x64, 0x27, 0xb6, 0x1a, 0x9d, 0x78,
0x0b, 0xcc, 0xaf, 0x71, 0xa5, 0x9b, 0x93, 0x8e, 0x15, 0xaf, 0xac, 0x06, 0xaf, 0x76, 0xc1, 0x3e,
0x97, 0x8f, 0xab, 0x7a, 0x2b, 0xe0, 0xdf, 0x87, 0xb6, 0x8a, 0xa1, 0xb2, 0x6c, 0x34, 0x2c, 0xf7,
0xa1, 0xf7, 0x44, 0x04, 0x18, 0x65, 0x8a, 0x29, 0xea, 0xd1, 0xa6, 0xc8, 0xff, 0xd5, 0x00, 0x8b,
0x9c, 0x27, 0x56, 0x85, 0x38, 0xe7, 0x93, 0xe2, 0x20, 0xce, 0xa3, 0x69, 0xea, 0x19, 0x7d, 0x73,
0x60, 0xb2, 0x35, 0x99, 0xfb, 0x06, 0xb4, 0x2f, 0x94, 0xb6, 0xd5, 0x37, 0x07, 0x0e, 0xd3, 0x88,
0x5c, 0x0b, 0xf9, 0x05, 0x86, 0x3a, 0x04, 0x05, 0xe8, 0x76, 0x22, 0x70, 0x16, 0xac, 0x74, 0x18,
0x1a, 0x91, 0x3c, 0xcd, 0x67, 0x24, 0x57, 0x91, 0x68, 0x44, 0x01, 0x5c, 0xf0, 0xb4, 0xa2, 0x0f,
0x9d, 0xc9, 0x72, 0x3a, 0xe1, 0x61, 0xc9, 0x1f, 0x05, 0xfc, 0xdf, 0x0d, 0x9a, 0x2b, 0xaa, 0x1f,
0x36, 0x32, 0xfc, 0x26, 0x74, 0xa9, 0x57, 0x9e, 0x5d, 0x72, 0xa1, 0x03, 0xee, 0x10, 0x3e, 0xe7,
0xc2, 0xfd, 0x18, 0xda, 0xb2, 0x44, 0x5b, 0x7a, 0xb3, 0x34, 0x27, 0xb3, 0xca, 0xf4, 0xb5, 0x8a,
0xbd, 0x56, 0x83, 0xbd, 0x55, 0xb0, 0x76, 0x33, 0xd8, 0xbb, 0x60, 0x53, 0x1b, 0x14, 0xd2, 0xfb,
0xad, 0x96, 0x55, 0xb3, 0xa8, 0x5b, 0xfe, 0x19, 0xdc, 0x58, 0x7b, 0xb1, 0x7a, 0xc9, 0x58, 0x7f,
0xa9, 0xa6, 0x9b, 0xa3, 0xe9, 0x45, 0x33, 0x35, 0xc5, 0x10, 0x27, 0x19, 0x4e, 0x65, 0xbe, 0xbb,
0xac, 0xc2, 0xfe, 0x4f, 0x46, 0x6d, 0x57, 0xbe, 0x47, 0x53, 0x73, 0x12, 0x2f, 0x97, 0x3c, 0x9a,
0x6a, 0xd3, 0x25, 0xa4, 0xbc, 0x4d, 0x2f, 0xb4, 0xe9, 0xd6, 0xf4, 0x82, 0xb0, 0x48, 0x74, 0x05,
0x5b, 0x22, 0x21, 0xee, 0x2c, 0x91, 0xa7, 0xb9, 0xc0, 0x25, 0x46, 0x99, 0x4e, 0x41, 0x53, 0xe4,
0xde, 0x86, 0x4e, 0xc6, 0xe7, 0xcf, 0xa8, 0x49, 0x74, 0x25, 0x33, 0x3e, 0x7f, 0x88, 0x85, 0xfb,
0x16, 0x38, 0xb3, 0x00, 0xc3, 0xa9, 0x54, 0xa9, 0x72, 0x76, 0xa5, 0xe0, 0x21, 0x16, 0xfe, 0x1f,
0x06, 0xb4, 0xc7, 0x28, 0x2e, 0x51, 0xbc, 0xd2, 0x38, 0x6d, 0xae, 0x29, 0xf3, 0x25, 0x6b, 0xca,
0xda, 0xbe, 0xa6, 0xec, 0x7a, 0x4d, 0xed, 0x82, 0x3d, 0x16, 0x93, 0xa3, 0x91, 0xf4, 0xc8, 0x64,
0x0a, 0x10, 0x1b, 0x87, 0x93, 0x2c, 0xb8, 0x44, 0xbd, 0xbb, 0x34, 0xda, 0x98, 0xb2, 0xdd, 0x2d,
0x53, 0xf6, 0x47, 0x03, 0xda, 0xc7, 0xbc, 0x88, 0xf3, 0x6c, 0x83, 0x85, 0x7d, 0xe8, 0x0d, 0x93,
0x24, 0x0c, 0x26, 0x6b, 0x9d, 0xd7, 0x10, 0xd1, 0x8d, 0x47, 0x8d, 0xfc, 0xaa, 0xd8, 0x9a, 0x22,
0x1a, 0x37, 0x87, 0x72, 0x93, 0xa8, 0xb5, 0xd0, 0x18, 0x37, 0x6a, 0x81, 0x48, 0x25, 0x25, 0x61,
0x98, 0x67, 0xf1, 0x2c, 0x8c, 0xaf, 0x64, 0xb4, 0x5d, 0x56, 0x61, 0xff, 0xcf, 0x16, 0x58, 0xff,
0xd7, 0xf4, 0xdf, 0x01, 0x23, 0xd0, 0xc5, 0x36, 0x82, 0x6a, 0x17, 0x74, 0x1a, 0xbb, 0xc0, 0x83,
0x4e, 0x21, 0x78, 0x34, 0xc7, 0xd4, 0xeb, 0xca, 0xe9, 0x52, 0x42, 0xa9, 0x91, 0x7d, 0xa4, 0x96,
0x80, 0xc3, 0x4a, 0x58, 0xf5, 0x05, 0x34, 0xfa, 0xe2, 0x23, 0xbd, 0x2f, 0x7a, 0xd2, 0x23, 0x6f,
0x3d, 0x2d, 0xd7, 0xd7, 0xc4, 0x7f, 0x37, 0xd3, 0xff, 0x36, 0xc0, 0xae, 0x9a, 0xea, 0x70, 0xbd,
0xa9, 0x0e, 0xeb, 0xa6, 0x1a, 0x1d, 0x94, 0x4d, 0x35, 0x3a, 0x20, 0xcc, 0x4e, 0xca, 0xa6, 0x62,
0x27, 0x54, 0xac, 0x07, 0x22, 0xce, 0x93, 0x83, 0x42, 0x55, 0xd5, 0x61, 0x15, 0x26, 0x26, 0x7e,
0xbb, 0x40, 0xa1, 0x53, 0xed, 0x30, 0x8d, 0x88, 0xb7, 0xc7, 0x72, 0xe0, 0xa8, 0xe4, 0x2a, 0xe0,
0xbe, 0x07, 0x36, 0xa3, 0xe4, 0xc9, 0x0c, 0xaf, 0xd5, 0x45, 0x8a, 0x99, 0xd2, 0x92, 0x51, 0xf5,
0x9d, 0xa8, 0x09, 0x5c, 0x7e, 0x35, 0x7e, 0x08, 0xed, 0xf1, 0x22, 0x98, 0x65, 0xe5, 0xd6, 0x7d,
0xbd, 0x31, 0xb0, 0x82, 0x25, 0x4a, 0x1d, 0xd3, 0x57, 0xfc, 0xa7, 0xe0, 0x54, 0xc2, 0xda, 0x1d,
0xa3, 0xe9, 0x8e, 0x0b, 0xd6, 0x59, 0x14, 0x64, 0x65, 0xeb, 0xd2, 0x99, 0x82, 0x7d, 0x9a, 0xf3,
0x28, 0x0b, 0xb2, 0xa2, 0x6c, 0xdd, 0x12, 0xfb, 0xf7, 0xb4, 0xfb, 0x64, 0xee, 0x2c, 0x49, 0x50,
0xe8, 0x31, 0xa0, 0x80, 0x7c, 0x24, 0xbe, 0x42, 0x35, 0xc1, 0x4d, 0xa6, 0x80, 0xff, 0x1d, 0x38,
0xc3, 0x10, 0x45, 0xc6, 0xf2, 0x10, 0xb7, 0x6d, 0xd6, 0x6f, 0xc6, 0x4f, 0x1e, 0x97, 0x1e, 0xd0,
0xb9, 0x6e, 0x79, 0xf3, 0x5a, 0xcb, 0x3f, 0xe4, 0x09, 0x3f, 0x1a, 0x49, 0x9e, 0x9b, 0x4c, 0x23,
0xff, 0x67, 0x03, 0x2c, 0x9a, 0x2d, 0x0d, 0xd3, 0xd6, 0xcb, 0xe6, 0xd2, 0x89, 0x88, 0x2f, 0x83,
0x29, 0x8a, 0x32, 0xb8, 0x12, 0xcb, 0xa4, 0x4f, 0x16, 0x58, 0x2d, 0x70, 0x8d, 0x88, 0x6b, 0xf4,
0x51, 0x59, 0xf6, 0x52, 0x83, 0x6b, 0x24, 0x66, 0x4a, 0xe9, 0xbe, 0x0d, 0x30, 0xce, 0x13, 0x14,
0xc3, 0xe9, 0x32, 0x88, 0x64, 0xd1, 0xbb, 0xac, 0x21, 0xf1, 0xef, 0xab, 0xcf, 0xd4, 0x8d, 0x09,
0x65, 0x6c, 0xff, 0xa4, 0xbd, 0xee, 0xb9, 0xff, 0x8b, 0x01, 0x9d, 0x47, 0x3c, 0x49, 0x82, 0x68,
0xbe, 0x16, 0x85, 0xf1, 0xc2, 0x28, 0x5a, 0x6b, 0x51, 0xec, 0xc3, 0x6e, 0x79, 0x67, 0xed, 0x7d,
0x95, 0x85, 0xad, 0x3a, 0x9d, 0x51, 0xab, 0x2a, 0xd6, 0xab, 0x7c, 0xc3, 0x9e, 0xae, 0xdf, 0xd9,
0x56, 0xf0, 0x8d, 0xaa, 0xf4, 0xa1, 0xa7, 0xff, 0x7b, 0xc8, 0x2f, 0x79, 0x3d, 0x54, 0x1b, 0x22,
0x7f, 0x1f, 0xda, 0x87, 0x71, 0x34, 0x0b, 0xe6, 0xee, 0x00, 0xac, 0x61, 0x9e, 0x2d, 0xa4, 0xc5,
0xde, 0xfe, 0x6e, 0xa3, 0xf1, 0xf3, 0x6c, 0xa1, 0xee, 0x30, 0x79, 0xc3, 0xff, 0x02, 0xa0, 0x96,
0xd1, 0x1f, 0x97, 0xba, 0x1a, 0x8f, 0xf1, 0x8a, 0x28, 0x93, 0x4a, 0x2b, 0x5d, 0xb6, 0x45, 0xe3,
0x7f, 0x09, 0xce, 0x41, 0x1e, 0x84, 0xd3, 0xa3, 0x68, 0x16, 0xd3, 0xe8, 0x38, 0x47, 0x91, 0xd6,
0xf5, 0x2a, 0x21, 0xa5, 0x9b, 0xa6, 0x48, 0xd5, 0x43, 0x1a, 0x5d, 0xb4, 0xe5, 0x7f, 0xbf, 0x7b,
0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xfe, 0xe9, 0xd1, 0x8f, 0x0d, 0x0e, 0x00, 0x00,
// 1558 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0xdb, 0x46,
0x12, 0x07, 0x25, 0x51, 0x12, 0x47, 0x4e, 0xce, 0xe0, 0xf9, 0x12, 0x5e, 0x0e, 0x38, 0xe8, 0x88,
0x3b, 0x9c, 0xee, 0x4f, 0x7c, 0x07, 0x05, 0x45, 0x8b, 0xa0, 0x0d, 0x20, 0x5b, 0x6d, 0xea, 0xc6,
0x89, 0x9d, 0x95, 0xed, 0x3e, 0x15, 0xc1, 0x4a, 0x1a, 0x49, 0x44, 0x28, 0x92, 0x5d, 0x92, 0xb6,
0xd9, 0x0f, 0x53, 0xa0, 0x40, 0xfb, 0x05, 0x8a, 0xbe, 0xf4, 0xa9, 0xef, 0xfd, 0x10, 0x7d, 0xec,
0x57, 0x68, 0x1f, 0x8b, 0xd9, 0x5d, 0x52, 0x2b, 0x4b, 0x09, 0x52, 0xa0, 0xe8, 0xdb, 0xfe, 0x66,
0x86, 0xb3, 0xf3, 0x7f, 0x96, 0x70, 0x3b, 0x88, 0x32, 0x14, 0x11, 0x0f, 0xf7, 0x13, 0x11, 0x67,
0xb1, 0xdb, 0x2e, 0xb1, 0xff, 0x63, 0x0d, 0x9a, 0xa3, 0x38, 0x17, 0x13, 0x74, 0x6f, 0x43, 0xed,
0x68, 0xe8, 0x59, 0x5d, 0xab, 0x57, 0x67, 0xb5, 0xa3, 0xa1, 0xeb, 0x42, 0xe3, 0x19, 0x5f, 0xa2,
0x57, 0xeb, 0x5a, 0x3d, 0x87, 0xc9, 0x33, 0xd1, 0xce, 0x8a, 0x04, 0xbd, 0xba, 0xa2, 0xd1, 0xd9,
0xbd, 0x07, 0xed, 0xf3, 0x94, 0xb4, 0x2d, 0xd1, 0x6b, 0x48, 0x7a, 0x85, 0x89, 0x77, 0xca, 0xd3,
0xf4, 0x2a, 0x16, 0x53, 0xcf, 0x56, 0xbc, 0x12, 0xbb, 0xbb, 0x50, 0x3f, 0x67, 0xc7, 0x5e, 0x53,
0x92, 0xe9, 0xe8, 0x7a, 0xd0, 0x1a, 0xe2, 0x8c, 0xe7, 0x61, 0xe6, 0xb5, 0xba, 0x56, 0xaf, 0xcd,
0x4a, 0x48, 0x7a, 0xce, 0x30, 0xc4, 0xb9, 0xe0, 0x33, 0xaf, 0xad, 0xf4, 0x94, 0xd8, 0xdd, 0x07,
0xf7, 0x28, 0x4a, 0x71, 0x92, 0x0b, 0x1c, 0xbd, 0x0c, 0x92, 0x0b, 0x14, 0xc1, 0xac, 0xf0, 0x1c,
0xa9, 0x60, 0x0b, 0x87, 0x6e, 0x79, 0x8a, 0x19, 0xa7, 0xbb, 0x41, 0xaa, 0x2a, 0xa1, 0xeb, 0xc3,
0xce, 0x68, 0xc1, 0x05, 0x4e, 0x47, 0x38, 0x11, 0x98, 0x79, 0x1d, 0xc9, 0x5e, 0xa3, 0x91, 0xcc,
0x89, 0x98, 0xf3, 0x28, 0xf8, 0x8c, 0x67, 0x41, 0x1c, 0x79, 0x3b, 0x4a, 0xc6, 0xa4, 0x51, 0x94,
0x58, 0x1c, 0xa2, 0x77, 0x4b, 0x45, 0x89, 0xce, 0xfe, 0x37, 0x16, 0x38, 0x43, 0x9e, 0x2e, 0xc6,
0x31, 0x17, 0xd3, 0x37, 0x8a, 0xf5, 0x7d, 0xb0, 0x27, 0x18, 0x86, 0xa9, 0x57, 0xef, 0xd6, 0x7b,
0x9d, 0xfe, 0xdd, 0xfd, 0x2a, 0x89, 0x95, 0x9e, 0x43, 0x0c, 0x43, 0xa6, 0xa4, 0xdc, 0xff, 0x83,
0x93, 0xe1, 0x32, 0x09, 0x79, 0x86, 0xa9, 0xd7, 0x90, 0x9f, 0xb8, 0xab, 0x4f, 0xce, 0x34, 0x8b,
0xad, 0x84, 0x36, 0x5c, 0xb1, 0x37, 0x5d, 0xf1, 0xbf, 0xad, 0xc3, 0xad, 0xb5, 0xeb, 0xdc, 0x1d,
0xb0, 0xae, 0xa5, 0xe5, 0x36, 0xb3, 0xae, 0x09, 0x15, 0xd2, 0x6a, 0x9b, 0x59, 0x05, 0xa1, 0x2b,
0x59, 0x1b, 0x36, 0xb3, 0xae, 0x08, 0x2d, 0x64, 0x45, 0xd8, 0xcc, 0x5a, 0xb8, 0xff, 0x82, 0xd6,
0xa7, 0x39, 0x8a, 0x00, 0x53, 0xcf, 0x96, 0xd6, 0xfd, 0x61, 0x65, 0xdd, 0xf3, 0x1c, 0x45, 0xc1,
0x4a, 0x3e, 0x45, 0x43, 0x56, 0x93, 0x2a, 0x0d, 0x79, 0x26, 0x5a, 0x46, 0x95, 0xd7, 0x52, 0x34,
0x3a, 0xeb, 0x28, 0xaa, 0x7a, 0xa0, 0x28, 0xbe, 0x05, 0x0d, 0x7e, 0x8d, 0xa9, 0xe7, 0x48, 0xfd,
0x7f, 0x7b, 0x45, 0xc0, 0xf6, 0x07, 0xd7, 0x98, 0xbe, 0x1f, 0x65, 0xa2, 0x60, 0x52, 0xdc, 0xfd,
0x27, 0x34, 0x27, 0x71, 0x18, 0x8b, 0xd4, 0x83, 0x9b, 0x86, 0x1d, 0x12, 0x9d, 0x69, 0xb6, 0xdb,
0x83, 0x66, 0x88, 0x73, 0x8c, 0xa6, 0xb2, 0x32, 0x3a, 0xfd, 0xdd, 0x95, 0xe0, 0xb1, 0xa4, 0x33,
0xcd, 0x77, 0x1f, 0xc2, 0x4e, 0xc6, 0xc7, 0x21, 0x9e, 0x24, 0x14, 0xc5, 0x54, 0x56, 0x49, 0xa7,
0x7f, 0xc7, 0xc8, 0x87, 0xc1, 0x65, 0x6b, 0xb2, 0xf7, 0x1e, 0x83, 0x53, 0x59, 0x48, 0x4d, 0xf2,
0x12, 0x0b, 0x19, 0x6f, 0x87, 0xd1, 0xd1, 0xfd, 0x3b, 0xd8, 0x97, 0x3c, 0xcc, 0x55, 0xad, 0x74,
0xfa, 0xb7, 0x57, 0x3a, 0x07, 0xd7, 0x41, 0xca, 0x14, 0xf3, 0x61, 0xed, 0x1d, 0xcb, 0xff, 0xc1,
0x82, 0x1d, 0xf3, 0x1e, 0xf7, 0xaf, 0x00, 0x59, 0xb0, 0xc4, 0x0f, 0x62, 0xb1, 0xe4, 0x99, 0xd6,
0x69, 0x50, 0xdc, 0x7f, 0xc3, 0xee, 0x25, 0x8a, 0x2c, 0x98, 0xf0, 0xf0, 0x2c, 0x58, 0x22, 0xe9,
0x93, 0xb7, 0xb4, 0xd9, 0x06, 0xdd, 0xbd, 0x0f, 0xcd, 0x34, 0x16, 0xd9, 0x41, 0x21, 0xf3, 0xdd,
0xe9, 0xff, 0xe9, 0x86, 0x6f, 0x87, 0x71, 0x98, 0x2f, 0x23, 0xa6, 0x85, 0xa8, 0x81, 0xaf, 0x04,
0x4f, 0x92, 0x20, 0x9a, 0x97, 0x43, 0xa2, 0xc4, 0xee, 0xdb, 0xd0, 0x99, 0x48, 0x69, 0x2a, 0xfb,
0xb2, 0x3a, 0x5e, 0xa1, 0xcf, 0x94, 0xf4, 0x47, 0xd0, 0x31, 0x78, 0x54, 0xcf, 0xe5, 0x37, 0xb2,
0x99, 0x94, 0x83, 0x6b, 0x34, 0xb7, 0x0b, 0x9d, 0x69, 0x90, 0x26, 0x21, 0x2f, 0x8c, 0x7e, 0x33,
0x49, 0xfe, 0x1c, 0x6c, 0x99, 0x75, 0xa3, 0x47, 0x9d, 0xb2, 0x47, 0xe5, 0xec, 0xab, 0x19, 0xb3,
0x6f, 0x17, 0xea, 0x1f, 0xe2, 0xb5, 0x1e, 0x87, 0x74, 0xac, 0x3a, 0xb9, 0x61, 0x74, 0xf2, 0x1e,
0xd8, 0x17, 0x32, 0x65, 0xaa, 0xc3, 0x14, 0xf0, 0x1f, 0x41, 0x53, 0x55, 0x4d, 0xa5, 0xd9, 0x32,
0x34, 0x77, 0xa1, 0x73, 0x22, 0x02, 0x8c, 0x32, 0xd5, 0x9b, 0xda, 0x50, 0x83, 0xe4, 0x7f, 0x6d,
0x41, 0x43, 0xa6, 0xc2, 0x87, 0x9d, 0x10, 0xe7, 0x7c, 0x52, 0x1c, 0xc4, 0x79, 0x34, 0x4d, 0x3d,
0xab, 0x5b, 0xef, 0xd5, 0xd9, 0x1a, 0xcd, 0xbd, 0x03, 0xcd, 0xb1, 0xe2, 0xd6, 0xba, 0xf5, 0x9e,
0xc3, 0x34, 0x22, 0xd3, 0x42, 0x3e, 0xc6, 0x50, 0xbb, 0xa0, 0x00, 0x49, 0x27, 0x02, 0x67, 0xc1,
0xb5, 0x76, 0x43, 0x23, 0xa2, 0xa7, 0xf9, 0x8c, 0xe8, 0xca, 0x13, 0x8d, 0xc8, 0x81, 0x31, 0x4f,
0xab, 0x86, 0xa5, 0x33, 0x69, 0x4e, 0x27, 0x3c, 0x2c, 0x3b, 0x56, 0x01, 0xff, 0x3b, 0x8b, 0x26,
0xb9, 0x9a, 0x40, 0x1b, 0x11, 0xfe, 0x33, 0xb4, 0x69, 0x3a, 0xbd, 0xb8, 0xe4, 0x42, 0x3b, 0xdc,
0x22, 0x7c, 0xc1, 0x85, 0xfb, 0x3f, 0x68, 0xca, 0xc2, 0xde, 0x32, 0x0d, 0x4b, 0x75, 0x32, 0xaa,
0x4c, 0x8b, 0x55, 0xf3, 0xa2, 0x61, 0xcc, 0x8b, 0xca, 0x59, 0xdb, 0x74, 0xf6, 0x3e, 0xd8, 0x34,
0x78, 0x0a, 0x69, 0xfd, 0x56, 0xcd, 0x6a, 0x3c, 0x29, 0x29, 0xff, 0x1c, 0x6e, 0xad, 0xdd, 0x58,
0xdd, 0x64, 0xad, 0xdf, 0xb4, 0x6a, 0x52, 0x47, 0x37, 0x25, 0x35, 0x41, 0x8a, 0x21, 0x4e, 0x32,
0x9c, 0xca, 0x78, 0xb7, 0x59, 0x85, 0xfd, 0x2f, 0xac, 0x95, 0x5e, 0x79, 0x1f, 0xed, 0xa9, 0x49,
0xbc, 0x5c, 0xf2, 0x68, 0xaa, 0x55, 0x97, 0x90, 0xe2, 0x36, 0x1d, 0x6b, 0xd5, 0xb5, 0xe9, 0x98,
0xb0, 0x48, 0x74, 0x06, 0x6b, 0x22, 0xa1, 0xda, 0x59, 0x22, 0x4f, 0x73, 0x81, 0x4b, 0x8c, 0x32,
0x1d, 0x02, 0x93, 0xe4, 0xde, 0x85, 0x56, 0xc6, 0xe7, 0x2f, 0x68, 0xb4, 0xe8, 0x4c, 0x66, 0x7c,
0xfe, 0x04, 0x0b, 0xf7, 0x2f, 0xe0, 0xcc, 0x02, 0x0c, 0xa7, 0x92, 0xa5, 0xd2, 0xd9, 0x96, 0x84,
0x27, 0x58, 0xf8, 0x3f, 0x5b, 0xd0, 0x1c, 0xa1, 0xb8, 0x44, 0xf1, 0x46, 0x0b, 0xcc, 0x7c, 0x18,
0xd4, 0x5f, 0xf3, 0x30, 0x68, 0x6c, 0x7f, 0x18, 0xd8, 0xab, 0x87, 0xc1, 0x1e, 0xd8, 0x23, 0x31,
0x39, 0x1a, 0x4a, 0x8b, 0xea, 0x4c, 0x01, 0xaa, 0xc6, 0xc1, 0x24, 0x0b, 0x2e, 0x51, 0xbf, 0x16,
0x34, 0xda, 0xd8, 0x6b, 0xed, 0x2d, 0x2b, 0xfa, 0x57, 0x3e, 0x1a, 0xfc, 0xcf, 0x2d, 0x68, 0x1e,
0xf3, 0x22, 0xce, 0xb3, 0x8d, 0xaa, 0xed, 0x42, 0x67, 0x90, 0x24, 0x61, 0x30, 0x59, 0xeb, 0x54,
0x83, 0x44, 0x12, 0x4f, 0x8d, 0x7c, 0xa8, 0x58, 0x98, 0x24, 0x1a, 0xea, 0x87, 0x72, 0xd7, 0xab,
0xc5, 0x6d, 0x0c, 0x75, 0xb5, 0xe2, 0x25, 0x93, 0x82, 0x36, 0xc8, 0xb3, 0x78, 0x16, 0xc6, 0x57,
0x32, 0x3a, 0x6d, 0x56, 0x61, 0xff, 0xfb, 0x1a, 0x34, 0x7e, 0xaf, 0xfd, 0xbc, 0x03, 0x56, 0xa0,
0x8b, 0xc3, 0x0a, 0xaa, 0x6d, 0xdd, 0x32, 0xb6, 0xb5, 0x07, 0xad, 0x42, 0xf0, 0x68, 0x8e, 0xa9,
0xd7, 0x96, 0xd3, 0xa8, 0x84, 0x92, 0x23, 0xfb, 0x4e, 0xad, 0x69, 0x87, 0x95, 0xb0, 0xea, 0x23,
0x30, 0xfa, 0xe8, 0xbf, 0x7a, 0xa3, 0x77, 0xa4, 0x45, 0xde, 0x7a, 0x58, 0x6e, 0x2e, 0xf2, 0xdf,
0x6e, 0x73, 0xfe, 0x64, 0x81, 0x5d, 0x35, 0xe1, 0xe1, 0x7a, 0x13, 0x1e, 0xae, 0x9a, 0x70, 0x78,
0x50, 0x36, 0xe1, 0xf0, 0x80, 0x30, 0x3b, 0x2d, 0x9b, 0x90, 0x9d, 0x52, 0xb2, 0x1e, 0x8b, 0x38,
0x4f, 0x0e, 0x0a, 0x95, 0x55, 0x87, 0x55, 0x98, 0x2a, 0xf7, 0xe3, 0x05, 0x0a, 0x1d, 0x6a, 0x87,
0x69, 0x44, 0x75, 0x7e, 0x2c, 0x07, 0x94, 0x0a, 0xae, 0x02, 0xee, 0x3f, 0xc0, 0x66, 0x14, 0x3c,
0x19, 0xe1, 0xb5, 0xbc, 0x48, 0x32, 0x53, 0x5c, 0x52, 0xaa, 0x5e, 0xf2, 0xba, 0xe0, 0xcb, 0x77,
0xfd, 0x7f, 0xa0, 0x39, 0x5a, 0x04, 0xb3, 0xac, 0x7c, 0x17, 0xfd, 0xd1, 0x18, 0x70, 0xc1, 0x12,
0x25, 0x8f, 0x69, 0x11, 0xff, 0x39, 0x38, 0x15, 0x71, 0x65, 0x8e, 0x65, 0x9a, 0xe3, 0x42, 0xe3,
0x3c, 0x0a, 0xb2, 0xb2, 0xd5, 0xe9, 0x4c, 0xce, 0x3e, 0xcf, 0x79, 0x94, 0x05, 0x59, 0x51, 0xb6,
0x7a, 0x89, 0xfd, 0x07, 0xda, 0x7c, 0x52, 0x77, 0x9e, 0x24, 0x28, 0xf4, 0xd8, 0x50, 0x40, 0x5e,
0x12, 0x5f, 0xa1, 0x9a, 0xf8, 0x75, 0xa6, 0x80, 0xff, 0x09, 0x38, 0x83, 0x10, 0x45, 0xc6, 0xf2,
0x10, 0xb7, 0x6d, 0xe2, 0x8f, 0x46, 0x27, 0xcf, 0x4a, 0x0b, 0xe8, 0xbc, 0x1a, 0x11, 0xf5, 0x1b,
0x23, 0xe2, 0x09, 0x4f, 0xf8, 0xd1, 0x50, 0xd6, 0x79, 0x9d, 0x69, 0xe4, 0x7f, 0x69, 0x41, 0x83,
0x66, 0x91, 0xa1, 0xba, 0xf1, 0xba, 0x39, 0x76, 0x2a, 0xe2, 0xcb, 0x60, 0x8a, 0xa2, 0x74, 0xae,
0xc4, 0x32, 0xe8, 0x93, 0x05, 0x56, 0x0b, 0x5f, 0x23, 0xaa, 0x35, 0x7a, 0xf6, 0x97, 0xbd, 0x64,
0xd4, 0x1a, 0x91, 0x99, 0x62, 0xd2, 0x83, 0x6c, 0x94, 0x27, 0x28, 0x06, 0xd3, 0x65, 0x10, 0xc9,
0xa4, 0xb7, 0x99, 0x41, 0xf1, 0x1f, 0xa9, 0x1f, 0x89, 0x8d, 0x89, 0x66, 0x6d, 0xff, 0xe9, 0xb8,
0x69, 0xb9, 0xff, 0x95, 0x05, 0xad, 0xa7, 0xfa, 0x95, 0x65, 0x7a, 0x61, 0xbd, 0xd2, 0x8b, 0xda,
0x9a, 0x17, 0x7d, 0xd8, 0x2b, 0x65, 0xd6, 0xee, 0x57, 0x51, 0xd8, 0xca, 0xd3, 0x11, 0x6d, 0x54,
0xc9, 0x7a, 0x93, 0xbf, 0x8c, 0xb3, 0x75, 0x99, 0x6d, 0x09, 0xdf, 0xc8, 0x4a, 0x17, 0x3a, 0xfa,
0xef, 0x50, 0xfe, 0x6b, 0xe9, 0xa1, 0x6a, 0x90, 0xfc, 0x3e, 0x34, 0x0f, 0xe3, 0x68, 0x16, 0xcc,
0xdd, 0x1e, 0x34, 0x06, 0x79, 0xb6, 0x90, 0x1a, 0x3b, 0xfd, 0x3d, 0xa3, 0xf1, 0xf3, 0x6c, 0xa1,
0x64, 0x98, 0x94, 0xf0, 0xdf, 0x05, 0x58, 0xd1, 0x68, 0x4b, 0xac, 0xb2, 0xf1, 0x0c, 0xaf, 0xa8,
0x64, 0x52, 0xa9, 0xa5, 0xcd, 0xb6, 0x70, 0xfc, 0xf7, 0xc0, 0x39, 0xc8, 0x83, 0x70, 0x7a, 0x14,
0xcd, 0x62, 0x1a, 0x1d, 0x17, 0x28, 0xd2, 0x55, 0xbe, 0x4a, 0x48, 0xe1, 0xa6, 0x29, 0x52, 0xf5,
0x90, 0x46, 0xe3, 0xa6, 0xfc, 0x3b, 0x7f, 0xf0, 0x4b, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe8, 0x64,
0x6b, 0x1b, 0xaf, 0x0f, 0x00, 0x00,
}

View File

@ -37,6 +37,20 @@ message DashboardCell {
map<string, Axis> axes = 9; // Axes represent the graphical viewport for a cell's visualizations
repeated Color colors = 10; // Colors represent encoding data values to color
Legend legend = 11; // Legend is summary information for a cell
TableOptions tableOptions = 12; // TableOptions for visualization of cell with type 'table'
}
message TableOptions {
string timeFormat = 1; // format for time
bool verticalTimeAxis = 2; // time axis should be a column not row
TableColumn sortBy = 3; // which column should a table be sorted by
string wrapping = 4; // option for text wrapping
repeated TableColumn columnNames = 5; // names and renames for columns
}
message TableColumn {
string internalName = 1; // name of column
string displayName = 2; // what column is renamed to
}
message Color {
@ -95,6 +109,7 @@ message Server {
int64 SrcID = 6; // SrcID is the ID of the data source
bool Active = 7; // is this the currently active server for the source
string Organization = 8; // Organization is the organization ID that resource belongs to
bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client
}
message Layout {

View File

@ -76,12 +76,13 @@ func TestMarshalSourceWithSecret(t *testing.T) {
func TestMarshalServer(t *testing.T) {
v := chronograf.Server{
ID: 12,
SrcID: 2,
Name: "Fountain of Truth",
Username: "docbrown",
Password: "1 point twenty-one g1g@w@tts",
URL: "http://oldmanpeabody.mall.io:9092",
ID: 12,
SrcID: 2,
Name: "Fountain of Truth",
Username: "docbrown",
Password: "1 point twenty-one g1g@w@tts",
URL: "http://oldmanpeabody.mall.io:9092",
InsecureSkipVerify: true,
}
var vv chronograf.Server
@ -193,6 +194,10 @@ func Test_MarshalDashboard(t *testing.T) {
Value: "100",
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "",
ColumnNames: []chronograf.TableColumn{},
},
},
},
Templates: []chronograf.Template{},
@ -255,6 +260,9 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static",
Orientation: "bottom",
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
Type: "line",
},
},
@ -309,6 +317,10 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static",
Orientation: "bottom",
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
},
Type: "line",
},
},
@ -369,6 +381,9 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
},
},
Type: "line",
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
},
},
Templates: []chronograf.Template{},
@ -418,6 +433,10 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
Value: "100",
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
},
Type: "line",
},
},
@ -434,3 +453,40 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
}
}
func Test_MarshalDashboard_WithEmptyCellType(t *testing.T) {
dashboard := chronograf.Dashboard{
ID: 1,
Cells: []chronograf.DashboardCell{
{
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
},
},
}
expected := chronograf.Dashboard{
ID: 1,
Cells: []chronograf.DashboardCell{
{
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
Type: "line",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{},
CellColors: []chronograf.CellColor{},
TableOptions: chronograf.TableOptions{
ColumnNames: []chronograf.TableColumn{},
},
},
},
Templates: []chronograf.Template{},
}
var actual chronograf.Dashboard
if buf, err := internal.MarshalDashboard(dashboard); err != nil {
t.Fatal("Error marshaling dashboard: err", err)
} else if err := internal.UnmarshalDashboard(buf, &actual); err != nil {
t.Fatal("Error unmarshaling dashboard: err:", err)
} else if !cmp.Equal(expected, actual) {
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
}
}

View File

@ -20,22 +20,24 @@ func TestServerStore(t *testing.T) {
srcs := []chronograf.Server{
chronograf.Server{
Name: "Of Truth",
SrcID: 10,
Username: "marty",
Password: "I❤ jennifer parker",
URL: "toyota-hilux.lyon-estates.local",
Active: false,
Organization: "133",
Name: "Of Truth",
SrcID: 10,
Username: "marty",
Password: "I❤ jennifer parker",
URL: "toyota-hilux.lyon-estates.local",
Active: false,
Organization: "133",
InsecureSkipVerify: true,
},
chronograf.Server{
Name: "HipToBeSquare",
SrcID: 12,
Username: "calvinklein",
Password: "chuck b3rry",
URL: "toyota-hilux.lyon-estates.local",
Active: false,
Organization: "133",
Name: "HipToBeSquare",
SrcID: 12,
Username: "calvinklein",
Password: "chuck b3rry",
URL: "toyota-hilux.lyon-estates.local",
Active: false,
Organization: "133",
InsecureSkipVerify: false,
},
}

View File

@ -150,26 +150,26 @@ func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.U
}
// Delete a user from the UsersStore
func (s *UsersStore) Delete(ctx context.Context, usr *chronograf.User) error {
_, err := s.get(ctx, usr.ID)
func (s *UsersStore) Delete(ctx context.Context, u *chronograf.User) error {
_, err := s.get(ctx, u.ID)
if err != nil {
return err
}
return s.client.db.Update(func(tx *bolt.Tx) error {
return tx.Bucket(UsersBucket).Delete(u64tob(usr.ID))
return tx.Bucket(UsersBucket).Delete(u64tob(u.ID))
})
}
// Update a user
func (s *UsersStore) Update(ctx context.Context, usr *chronograf.User) error {
_, err := s.get(ctx, usr.ID)
func (s *UsersStore) Update(ctx context.Context, u *chronograf.User) error {
_, err := s.get(ctx, u.ID)
if err != nil {
return err
}
return s.client.db.Update(func(tx *bolt.Tx) error {
if v, err := internal.MarshalUser(usr); err != nil {
if v, err := internal.MarshalUser(u); err != nil {
return err
} else if err := tx.Bucket(UsersBucket).Put(u64tob(usr.ID), v); err != nil {
} else if err := tx.Bucket(UsersBucket).Put(u64tob(u.ID), v); err != nil {
return err
}
return nil

View File

@ -56,6 +56,7 @@
]
}
],
"colors": [],
"type": "single-stat"
},
{
@ -73,6 +74,7 @@
]
}
],
"colors": [],
"type": "single-stat"
},
{

View File

@ -117,6 +117,7 @@
"h": 4,
"i": "0fa47984-825b-46f1-9ca5-0366e3220008",
"name": "Mesos Master Uptime",
"colors": [],
"type": "single-stat",
"queries": [
{

View File

@ -35,6 +35,9 @@ const (
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
ErrConfigNotFound = Error("cannot find configuration")
ErrAnnotationNotFound = Error("annotation not found")
ErrInvalidCellOptionsText = Error("invalid text wrapping option. Valid wrappings are 'truncate', 'wrap', and 'single line'")
ErrInvalidCellOptionsSort = Error("cell options sortby cannot be empty'")
ErrInvalidCellOptionsColumns = Error("cell options columns cannot be empty'")
)
// Error is a domain error encountered while processing chronograf requests
@ -543,17 +546,33 @@ type Legend struct {
// DashboardCell holds visual and query information for a cell
type DashboardCell struct {
ID string `json:"i"`
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []DashboardQuery `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
CellColors []CellColor `json:"colors"`
Legend Legend `json:"legend"`
ID string `json:"i"`
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []DashboardQuery `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
CellColors []CellColor `json:"colors"`
Legend Legend `json:"legend"`
TableOptions TableOptions `json:"tableOptions,omitempty"`
}
// TableColumn is a column in a DashboardCell of type Table
type TableColumn struct {
InternalName string `json:"internalName"`
DisplayName string `json:"displayName"`
}
// TableOptions is a type of options for a DashboardCell with type Table
type TableOptions struct {
TimeFormat string `json:"timeFormat"`
VerticalTimeAxis bool `json:"verticalTimeAxis"`
SortBy TableColumn `json:"sortBy"`
Wrapping string `json:"wrapping"`
ColumnNames []TableColumn `json:"columnNames"`
}
// DashboardsStore is the storage and retrieval of dashboards
@ -572,15 +591,16 @@ type DashboardsStore interface {
// Cell is a rectangle and multiple time series queries to visualize.
type Cell struct {
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
I string `json:"i"`
Name string `json:"name"`
Queries []Query `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
I string `json:"i"`
Name string `json:"name"`
Queries []Query `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
CellColors []CellColor `json:"colors"`
}
// Layout is a collection of Cells for visualization

View File

@ -1 +1 @@
**We've moved our documentation!** Check out the latest [authentication content](https://docs.influxdata.com/chronograf/latest/administration/security-best-practices/#chronograf-with-oauth-2-0-authentication) on InfluxData's [main docs site](https://docs.influxdata.com/chronograf/latest/).
**We've moved our documentation!** Check out the latest [authentication content](https://docs.influxdata.com/chronograf/latest/administration/managing-security/#oauth-2-0-providers-with-jwt-tokens) on InfluxData's [main docs site](https://docs.influxdata.com/chronograf/latest/).

View File

@ -12,10 +12,6 @@ import (
// SortTemplates the templates by size, then type, then value.
func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar {
sort.Slice(ts, func(i, j int) bool {
if ts[i].Var == ":interval:" {
return false
}
if len(ts[i].Values) != len(ts[j].Values) {
return len(ts[i].Values) < len(ts[j].Values)
}
@ -63,20 +59,6 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
tv[t.Values[i].Type] = t.Values[i].Value
}
if pts, ok := tv["points"]; ok {
points, err := strconv.ParseInt(pts, 0, 64)
if err != nil {
return "", err
}
dur, err := ParseTime(query, now)
if err != nil {
return "", err
}
interval := AutoInterval(points, dur)
return strings.Replace(query, t.Var, interval, -1), nil
}
if res, ok := tv["resolution"]; ok {
resolution, err := strconv.ParseInt(res, 0, 64)
if err != nil {
@ -101,22 +83,6 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
return query, nil
}
func AutoInterval(points int64, duration time.Duration) string {
// The function is: ((total_seconds * millisecond_converstion) / group_by) = pixels / 3
// Number of points given the pixels
pixels := float64(points)
msPerPixel := float64(duration/time.Millisecond) / pixels
secPerPixel := float64(duration/time.Second) / pixels
if secPerPixel < 1.0 {
if msPerPixel < 1.0 {
msPerPixel = 1.0
}
return strconv.FormatInt(int64(msPerPixel), 10) + "ms"
}
// If groupby is more than 1 second round to the second
return strconv.FormatInt(int64(secPerPixel), 10) + "s"
}
// AutoGroupBy generates the time to group by in order to decimate the number of
// points returned in a query
func AutoGroupBy(resolution, pixelsPerPoint int64, duration time.Duration) string {

View File

@ -125,38 +125,6 @@ func TestTemplateReplace(t *testing.T) {
},
want: `SELECT :field: FROM "cpu"`,
},
{
name: "auto interval",
query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`,
vars: []chronograf.TemplateVar{
{
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "333",
Type: "points",
},
},
},
},
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
},
{
name: "auto interval",
query: `SELECT derivative(mean(usage_idle),:interval:) from "cpu" where time > now() - 4320h group by time(:interval:)`,
vars: []chronograf.TemplateVar{
{
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "333",
Type: "points",
},
},
},
},
want: `SELECT derivative(mean(usage_idle),46702s) from "cpu" where time > now() - 4320h group by time(46702s)`,
},
{
name: "auto group by",
query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by :interval:`,
@ -165,7 +133,7 @@ func TestTemplateReplace(t *testing.T) {
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "999",
Value: "1000",
Type: "resolution",
},
{
@ -175,7 +143,7 @@ func TestTemplateReplace(t *testing.T) {
},
},
},
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46655s)`,
},
{
name: "auto group by without duration",
@ -185,7 +153,7 @@ func TestTemplateReplace(t *testing.T) {
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "999",
Value: "1000",
Type: "resolution",
},
{
@ -195,7 +163,7 @@ func TestTemplateReplace(t *testing.T) {
},
},
},
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`,
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46655s)`,
},
{
name: "auto group by with :dashboardTime:",

View File

@ -164,7 +164,8 @@ func TestServer(t *testing.T) {
"id": "5000",
"name": "Kapa 1",
"url": "http://localhost:9092",
"active": true,
"active": true,
"insecureSkipVerify": false,
"links": {
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
@ -222,7 +223,8 @@ func TestServer(t *testing.T) {
"id": "5000",
"name": "Kapa 1",
"url": "http://localhost:9092",
"active": true,
"active": true,
"insecureSkipVerify": false,
"links": {
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
@ -540,7 +542,16 @@ func TestServer(t *testing.T) {
"legend":{
"type": "static",
"orientation": "bottom"
},
},
"tableOptions":{
"timeFormat": "",
"verticalTimeAxis": false,
"sortBy":{
"internalName": "",
"displayName": ""},
"wrapping": "",
"columnNames": null
},
"links": {
"self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093"
}
@ -779,7 +790,17 @@ func TestServer(t *testing.T) {
"name": "comet",
"value": "100"
}
],
],
"tableOptions":{
"timeFormat":"",
"verticalTimeAxis":false,
"sortBy":{
"internalName":"",
"displayName":""
},
"wrapping":"",
"columnNames":null
},
"legend":{
"type": "static",
"orientation": "bottom"

View File

@ -2,6 +2,7 @@ package oauth2
import (
"encoding/json"
"fmt"
"net/http"
"github.com/influxdata/chronograf"
@ -61,7 +62,19 @@ func (h *Heroku) PrincipalID(provider *http.Client) (string, error) {
DefaultOrganization DefaultOrg `json:"default_organization"`
}
resp, err := provider.Get(HerokuAccountRoute)
req, err := http.NewRequest("GET", HerokuAccountRoute, nil)
// Requests fail to Heroku unless this Accept header is set.
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
resp, err := provider.Do(req)
if resp.StatusCode/100 != 2 {
err := fmt.Errorf(
"Unable to GET user data from %s. Status: %s",
HerokuAccountRoute,
resp.Status,
)
h.Logger.Error("", err)
return "", err
}
if err != nil {
h.Logger.Error("Unable to communicate with Heroku. err:", err)
return "", err

View File

@ -1,6 +1,7 @@
package oauth2
import (
"encoding/json"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
@ -13,15 +14,24 @@ import (
var testTime = time.Date(1985, time.October, 25, 18, 0, 0, 0, time.UTC)
type mockCallbackResponse struct {
AccessToken string `json:"access_token"`
}
// setupMuxTest produces an http.Client and an httptest.Server configured to
// use a particular http.Handler selected from a AuthMux. As this selection is
// done during the setup process, this configuration is performed by providing
// a function, and returning the desired handler. Cleanup is still the
// responsibility of the test writer, so the httptest.Server's Close() method
// should be deferred.
func setupMuxTest(selector func(*AuthMux) http.Handler) (*http.Client, *httptest.Server, *httptest.Server) {
func setupMuxTest(response interface{}, selector func(*AuthMux) http.Handler) (*http.Client, *httptest.Server, *httptest.Server) {
provider := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("content-type", "application/json")
rw.WriteHeader(http.StatusOK)
body, _ := json.Marshal(response)
rw.Write(body)
}))
now := func() time.Time {
@ -63,7 +73,9 @@ func teardownMuxTest(hc *http.Client, backend *httptest.Server, provider *httpte
func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) {
t.Parallel()
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
var response interface{}
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
return j.Logout()
})
defer teardownMuxTest(hc, ts, prov)
@ -100,7 +112,9 @@ func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) {
func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) {
t.Parallel()
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
var response interface{}
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
return j.Login() // Use Login handler for httptest server.
})
defer teardownMuxTest(hc, ts, prov)
@ -126,7 +140,8 @@ func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) {
}
func Test_AuthMux_Callback_SetsCookie(t *testing.T) {
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
response := mockCallbackResponse{AccessToken: "123"}
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
return j.Callback()
})
defer teardownMuxTest(hc, ts, prov)

View File

@ -532,7 +532,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
}
}
`))),
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"timeFormat":"","verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":""},"wrapping":"","columnNames":null},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
`,
},
{

View File

@ -16,7 +16,7 @@ type postKapacitorRequest struct {
URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true
Username string `json:"username,omitempty"` // Username for authentication to kapacitor
Password string `json:"password,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
Active bool `json:"active"`
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to
}
@ -55,7 +55,7 @@ type kapacitor struct {
URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092)
Username string `json:"username,omitempty"` // Username for authentication to kapacitor
Password string `json:"password,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
Active bool `json:"active"`
Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor
}
@ -225,7 +225,7 @@ type patchKapacitorRequest struct {
URL *string `json:"url,omitempty"` // URL for the kapacitor
Username *string `json:"username,omitempty"` // Username for kapacitor auth
Password *string `json:"password,omitempty"`
InsecureSkipVerify *bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted.
Active *bool `json:"active"`
}

View File

@ -30,6 +30,10 @@ func newLayoutResponse(layout chronograf.Layout) layoutResponse {
layout.Cells[idx].Axes = make(map[string]chronograf.Axis, len(axes))
}
if cell.CellColors == nil {
layout.Cells[idx].CellColors = []chronograf.CellColor{}
}
for _, axis := range axes {
if _, found := cell.Axes[axis]; !found {
layout.Cells[idx].Axes[axis] = chronograf.Axis{

View File

@ -76,12 +76,13 @@ func Test_Layouts(t *testing.T) {
Measurement: "influxdb",
Cells: []chronograf.Cell{
{
X: 0,
Y: 0,
W: 4,
H: 4,
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
Name: "A Graph",
X: 0,
Y: 0,
W: 4,
H: 4,
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
Name: "A Graph",
CellColors: []chronograf.CellColor{},
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Bounds: []string{},
@ -103,12 +104,13 @@ func Test_Layouts(t *testing.T) {
Measurement: "influxdb",
Cells: []chronograf.Cell{
{
X: 0,
Y: 0,
W: 4,
H: 4,
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
Name: "A Graph",
X: 0,
Y: 0,
W: 4,
H: 4,
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
CellColors: []chronograf.CellColor{},
Name: "A Graph",
},
},
},

View File

@ -3,7 +3,7 @@
"info": {
"title": "Chronograf",
"description": "API endpoints for Chronograf",
"version": "1.4.1.3"
"version": "1.4.2.3"
},
"schemes": ["http"],
"basePath": "/chronograf/v1",

View File

@ -6,7 +6,7 @@
"transform-runtime",
"lodash"
],
"presets": ["es2015", "react", "stage-0"],
"presets": ["env", "react", "stage-0"],
"env": {
"production": {
"plugins": [

View File

@ -1,20 +1,26 @@
{
parser: 'babel-eslint',
"parser": "babel-eslint",
plugins: [
'react',
'prettier',
'babel',
'jest',
],
extends: [
"prettier",
"prettier/react"
],
env: {
browser: true,
mocha: true,
"jest": true,
node: true,
es6: true,
},
globals: {
expect: true,
},
"parserOptions": {
parserOptions: {
ecmaFeatures: {
arrowFunctions: true,
binaryLiterals: true,
@ -244,6 +250,10 @@
'semi': false,
}],
// jest
'jest/no-disabled-tests': "warn",
'jest/no-focused-tests': "error",
// Babel
'babel/no-invalid-this': 1
},

25
ui/jest.config.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
projects: [
{
displayName: 'test',
testPathIgnorePatterns: [
'build',
'<rootDir>/node_modules/(?!(jest-test))',
],
modulePaths: ['<rootDir>', '<rootDir>/node_modules/'],
moduleDirectories: ['src'],
setupFiles: ['<rootDir>/test/setupTests.js'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
'^.+\\.js$': 'babel-jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
},
{
runner: 'jest-runner-eslint',
displayName: 'lint',
testMatch: ['<rootDir>/test/**/*.test.js'],
},
],
}

View File

@ -1,11 +1,10 @@
var webpack = require('webpack')
var path = require('path')
const webpack = require('webpack')
const path = require('path')
module.exports = function(config) {
config.set({
browsers: ['PhantomJS'],
singleRun: true,
frameworks: ['mocha', 'sinon-chai'],
frameworks: ['mocha'],
files: [
'node_modules/babel-polyfill/dist/polyfill.js',
'spec/spec-helper.js',
@ -42,10 +41,6 @@ module.exports = function(config) {
test: /sinon\/pkg\/sinon\.js/,
loader: 'imports?define=>false,require=>false',
},
{
test: /\.json$/,
loader: 'json',
},
],
},
externals: {
@ -61,7 +56,6 @@ module.exports = function(config) {
shared: path.resolve(__dirname, 'src', 'shared'),
style: path.resolve(__dirname, 'src', 'style'),
utils: path.resolve(__dirname, 'src', 'utils'),
sinon: 'sinon/pkg/sinon',
},
},
},

54
ui/mocks/dummy.ts Normal file
View File

@ -0,0 +1,54 @@
export const source = {
id: '2',
name: 'minikube-influx',
type: 'influx',
url: 'http://192.168.99.100:30400',
default: true,
telegraf: 'telegraf',
organization: 'default',
role: 'viewer',
links: {
self: '/chronograf/v1/sources/2',
kapacitors: '/chronograf/v1/sources/2/kapacitors',
proxy: '/chronograf/v1/sources/2/proxy',
queries: '/chronograf/v1/sources/2/queries',
write: '/chronograf/v1/sources/2/write',
permissions: '/chronograf/v1/sources/2/permissions',
users: '/chronograf/v1/sources/2/users',
databases: '/chronograf/v1/sources/2/dbs',
annotations: '/chronograf/v1/sources/2/annotations',
},
}
export const kapacitor = {
id: '1',
name: 'Test Kapacitor',
url: 'http://localhost:9092',
insecureSkipVerify: false,
active: true,
links: {
self: '/chronograf/v1/sources/47/kapacitors/1',
proxy: '/chronograf/v1/sources/47/kapacitors/1/proxy',
},
}
export const createKapacitorBody = {
name: 'Test Kapacitor',
url: 'http://localhost:9092',
insecureSkipVerify: false,
username: 'user',
password: 'pass',
}
export const updateKapacitorBody = {
name: 'Test Kapacitor',
url: 'http://localhost:9092',
insecureSkipVerify: false,
username: 'user',
password: 'pass',
active: true,
links: {
self: '/chronograf/v1/sources/47/kapacitors/1',
proxy: '/chronograf/v1/sources/47/kapacitors/1/proxy',
},
}

View File

@ -0,0 +1,6 @@
import {kapacitor} from 'mocks/dummy'
export const getKapacitor = jest.fn(() => Promise.resolve(kapacitor))
export const createKapacitor = jest.fn(() => Promise.resolve({data: kapacitor}))
export const updateKapacitor = jest.fn(() => Promise.resolve({data: kapacitor}))
export const pingKapacitor = jest.fn(() => Promise.resolve())

1
ui/mocks/utils/ajax.ts Normal file
View File

@ -0,0 +1 @@
export default jest.fn(() => Promise.resolve())

View File

@ -1,37 +0,0 @@
{
"src_folders": ["tests"],
"output_folder": "reports",
"custom_commands_path": "",
"custom_assertions_path": "",
"page_objects_path": "",
"globals_path": "",
"selenium": {
"start_process": false,
"host": "hub-cloud.browserstack.com",
"port": 80
},
"live_output" : true,
"test_settings": {
"default": {
"selenium_port": 80,
"selenium_host": "hub-cloud.browserstack.com",
"silent": false,
"screenshots": {
"enabled": true,
"path": "screenshots"
},
"desiredCapabilities": {
"browser": "chrome",
"build": "nightwatch-browserstack",
"browserstack.user": "${BROWSERSTACK_USER}",
"browserstack.key": "${BROWSERSTACK_KEY}",
"browserstack.debug": true,
"browserstack.local": true,
"resolution": "1280x1024"
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "chronograf-ui",
"version": "1.4.1-3",
"version": "1.4.2-3",
"private": false,
"license": "AGPL-3.0",
"description": "",
@ -9,17 +9,19 @@
"url": "github:influxdata/chronograf"
},
"scripts": {
"build": "yarn run clean && env NODE_ENV=production webpack --optimize-minimize --config ./webpack/prodConfig.js",
"build:dev": "webpack --config ./webpack/devConfig.js",
"start": "yarn run clean && webpack --watch --config ./webpack/devConfig.js",
"start:hmr": "webpack-dev-server --open --config ./webpack/devConfig.js",
"build": "yarn run clean && webpack --config ./webpack/prod.config.js",
"build:dev": "webpack --config ./webpack/dev.config.js",
"build:vendor": "webpack --config webpack/vendor.config.js",
"start": "yarn run clean && yarn run build:vendor && webpack --watch --config ./webpack/dev.config.js",
"start:fast": "webpack --watch --config ./webpack/dev.config.js",
"start:hmr": "webpack-dev-server --open --config ./webpack/dev.config.js",
"lint": "esw src/",
"test": "karma start",
"test:integration": "nightwatch tests --skip",
"test": "jest",
"test:lint": "yarn run lint; yarn run test",
"test:dev": "concurrently \"yarn run lint --watch\" \"yarn run test --no-single-run --reporters=verbose\"",
"clean": "rm -rf build/*",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
"test:watch": "jest --watch",
"clean": "rm -rf ./build/*",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix",
"eslint-check": "eslint --print-config .eslintrc | eslint-config-prettier-check"
},
"author": "",
"eslintConfig": {
@ -28,10 +30,18 @@
}
},
"devDependencies": {
"@types/chai": "^4.1.2",
"@types/enzyme": "^3.1.9",
"@types/jest": "^22.1.4",
"@types/lodash": "^4.14.104",
"@types/node": "^9.4.6",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.0.38",
"autoprefixer": "^6.3.1",
"babel-core": "^6.5.1",
"babel-eslint": "6.1.2",
"babel-loader": "^6.2.2",
"babel-jest": "^22.4.1",
"babel-loader": "^7.1.2",
"babel-plugin-lodash": "^2.0.1",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
@ -39,42 +49,36 @@
"babel-plugin-transform-react-remove-prop-types": "^0.2.1",
"babel-plugin-transform-runtime": "^6.5.0",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.5.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.16.0",
"babel-runtime": "^6.5.0",
"bower": "^1.7.7",
"chai": "^3.5.0",
"compression-webpack-plugin": "^1.1.8",
"concurrently": "^3.5.0",
"core-js": "^2.1.3",
"css-loader": "^0.23.1",
"envify": "^3.4.0",
"enzyme": "^2.4.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-15": "^1.0.5",
"eslint": "^3.14.1",
"eslint-loader": "1.6.1",
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-jest": "^21.12.2",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2",
"express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.7",
"fork-ts-checker-webpack-plugin": "^0.3.0",
"hanson": "^1.1.1",
"hson-loader": "^1.0.0",
"html-webpack-plugin": "^2.22.0",
"html-webpack-include-assets-plugin": "^1.0.2",
"html-webpack-plugin": "^2.30.1",
"imports-loader": "^0.6.5",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"jsdom": "^9.0.0",
"json-loader": "^0.5.4",
"karma": "^1.3.0",
"karma-cli": "^1.0.1",
"karma-mocha": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sinon-chai": "^1.2.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-verbose-reporter": "^0.0.6",
"karma-webpack": "^1.8.0",
"mocha": "^2.4.5",
"mocha-loader": "^0.7.1",
"mustache": "^2.2.1",
"json-loader": "^0.5.7",
"node-sass": "^4.5.3",
"on-build-webpack": "^0.1.0",
"postcss-browser-reporter": "^0.4.0",
@ -82,23 +86,26 @@
"postcss-loader": "^0.8.0",
"postcss-reporter": "^1.3.1",
"precss": "^1.4.0",
"prettier": "^1.5.3",
"prettier": "1.5.3",
"react-addons-test-utils": "^15.0.2",
"resolve-url-loader": "^1.6.0",
"sass-loader": "^3.2.0",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"react-test-renderer": "^15.6.1",
"resolve-url-loader": "^2.2.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.13.0",
"testem": "^1.2.1",
"uglify-js": "^2.6.1",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1"
"thread-loader": "^1.1.5",
"ts-jest": "^22.4.1",
"ts-loader": "^3.5.0",
"tslib": "^1.9.0",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^3.11.0",
"webpack-bundle-analyzer": "^2.10.1",
"webpack-dev-server": "^2.11.1"
},
"dependencies": {
"@skidding/react-codemirror": "^1.0.1",
"axios": "^0.13.1",
"bignumber.js": "^4.0.2",
"bootstrap": "^3.3.7",
"calculate-size": "^1.1.1",
"classnames": "^2.2.3",
"dygraphs": "2.1.0",
@ -106,30 +113,29 @@
"fast.js": "^0.1.1",
"fixed-data-table": "^0.6.1",
"he": "^1.1.1",
"jquery": "^3.1.0",
"lodash": "^4.3.0",
"moment": "^2.13.0",
"nano-date": "^2.0.1",
"node-uuid": "^1.4.7",
"prop-types": "^15.6.1",
"query-string": "^5.0.0",
"react": "^15.0.2",
"react-addons-shallow-compare": "^15.0.2",
"react-codemirror": "^1.0.0",
"react-component-resizable": "^1.1.0-rc1",
"react-custom-scrollbars": "^4.1.1",
"react-dimensions": "^1.2.0",
"react-dom": "^15.0.2",
"react-grid-layout": "^0.13.9",
"react-grid-layout": "^0.16.6",
"react-onclickoutside": "^5.2.0",
"react-redux": "^4.4.0",
"react-resizable": "^1.7.5",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.8",
"react-sparklines": "^1.4.2",
"react-tooltip": "^3.2.1",
"react-virtualized": "^9.18.5",
"redux": "^3.3.1",
"redux-auth-wrapper": "^1.0.0",
"redux-thunk": "^1.0.3",
"rome": "^2.1.22",
"updeep": "^0.13.0"
"uuid": "^3.2.1"
}
}

View File

@ -1,3 +0,0 @@
const context = require.context('./', true, /Spec\.js$/)
context.keys().forEach(context)
module.exports = context

View File

@ -1,13 +0,0 @@
window.then = function(cb, done) {
window.setTimeout(function() {
cb()
if (typeof done === 'function') {
done()
}
}, 0)
}
const chai = require('chai')
chai.use(require('sinon-chai'))
global.expect = chai.expect

View File

@ -1,42 +1,20 @@
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import React from 'react'
import PropTypes from 'prop-types'
import SideNav from 'src/side_nav'
import Notifications from 'shared/components/Notifications'
import {publishNotification} from 'shared/actions/notifications'
const App = ({children}) =>
<div className="chronograf-root">
<Notifications />
<SideNav />
{children}
</div>
const {func, node} = PropTypes
const {node} = PropTypes
const App = React.createClass({
propTypes: {
children: node.isRequired,
notify: func.isRequired,
},
App.propTypes = {
children: node.isRequired,
}
handleAddFlashMessage({type, text}) {
const {notify} = this.props
notify(type, text)
},
render() {
return (
<div className="chronograf-root">
<Notifications />
<SideNav />
{this.props.children &&
React.cloneElement(this.props.children, {
addFlashMessage: this.handleAddFlashMessage,
})}
</div>
)
},
})
const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(publishNotification, dispatch),
})
export default connect(null, mapDispatchToProps)(App)
export default App

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {withRouter} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
@ -14,9 +15,17 @@ import {showDatabases} from 'shared/apis/metaQuery'
import {getSourcesAsync} from 'shared/actions/sources'
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
import {publishNotification} from 'shared/actions/notifications'
import {notify as notifyAction} from 'shared/actions/notifications'
import {DEFAULT_HOME_PAGE} from 'shared/constants'
import {
NOTIFY_SOURCE_NO_LONGER_AVAILABLE,
NOTIFY_NO_SOURCES_AVAILABLE,
NOTIFY_UNABLE_TO_RETRIEVE_SOURCES,
NOTIFY_USER_REMOVED_FROM_ALL_ORGS,
NOTIFY_USER_REMOVED_FROM_CURRENT_ORG,
NOTIFY_ORG_HAS_NO_SOURCES,
} from 'shared/copy/notifications'
// Acts as a 'router middleware'. The main `App` component is responsible for
// getting the list of data nodes, but not every page requires them to function.
@ -84,10 +93,7 @@ class CheckSources extends Component {
}
if (!isFetching && isUsingAuth && !organizations.length) {
notify(
'error',
'You have been removed from all organizations. Please contact your administrator.'
)
notify(NOTIFY_USER_REMOVED_FROM_ALL_ORGS)
return router.push('/purgatory')
}
@ -95,7 +101,7 @@ class CheckSources extends Component {
me.superAdmin &&
!organizations.find(o => o.id === currentOrganization.id)
) {
notify('error', 'You were removed from your current organization')
notify(NOTIFY_USER_REMOVED_FROM_CURRENT_ORG)
return router.push('/purgatory')
}
@ -117,7 +123,7 @@ class CheckSources extends Component {
return router.push(`/sources/${sources[0].id}/${restString}`)
}
// if you're a viewer and there are no sources, go to purgatory.
notify('error', 'Organization has no sources configured')
notify(NOTIFY_ORG_HAS_NO_SOURCES)
return router.push('/purgatory')
}
@ -142,18 +148,12 @@ class CheckSources extends Component {
try {
const newSources = await getSources()
if (newSources.length) {
errorThrown(
error,
`Source ${source.name} is no longer available. Successfully connected to another source.`
)
errorThrown(error, NOTIFY_SOURCE_NO_LONGER_AVAILABLE(source.name))
} else {
errorThrown(
error,
`Unable to connect to source ${source.name}. No other sources available.`
)
errorThrown(error, NOTIFY_NO_SOURCES_AVAILABLE(source.name))
}
} catch (error2) {
errorThrown(error2, 'Unable to retrieve sources')
errorThrown(error2, NOTIFY_UNABLE_TO_RETRIEVE_SOURCES)
}
}
}
@ -247,7 +247,7 @@ const mapStateToProps = ({sources, auth}) => ({
const mapDispatchToProps = dispatch => ({
getSources: bindActionCreators(getSourcesAsync, dispatch),
errorThrown: bindActionCreators(errorThrownAction, dispatch),
notify: bindActionCreators(publishNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(

View File

@ -1,5 +1,5 @@
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import {
getUsers as getUsersAJAX,
@ -16,8 +16,14 @@ import {
deleteMapping as deleteMappingAJAX,
} from 'src/admin/apis/chronograf'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify} from 'shared/actions/notifications'
import {errorThrown} from 'shared/actions/errors'
import {
NOTIFY_MAPPING_DELETED,
NOTIFY_CHRONOGRAF_ORG_DELETED,
NOTIFY_CHRONOGRAF_USER_UPDATED,
NOTIFY_CHRONOGRAF_USER_DELETED,
} from 'shared/copy/notifications'
import {REVERT_STATE_DELAY} from 'shared/constants'
@ -177,12 +183,7 @@ export const deleteMappingAsync = mapping => async dispatch => {
dispatch(removeMapping(mapping))
try {
await deleteMappingAJAX(mapping)
dispatch(
publishAutoDismissingNotification(
'success',
`Mapping deleted: ${mapping.id} ${mapping.scheme}`
)
)
dispatch(notify(NOTIFY_MAPPING_DELETED(mapping.id, mapping.scheme)))
} catch (error) {
dispatch(errorThrown(error))
dispatch(addMapping(mapping))
@ -238,7 +239,7 @@ export const updateUserAsync = (
provider: null,
scheme: null,
})
dispatch(publishAutoDismissingNotification('success', successMessage))
dispatch(notify(NOTIFY_CHRONOGRAF_USER_UPDATED(successMessage)))
// it's not necessary to syncUser again but it's useful for good
// measure and for the clarity of insight in the redux story
dispatch(syncUser(user, data))
@ -256,12 +257,7 @@ export const deleteUserAsync = (
try {
await deleteUserAJAX(user)
dispatch(
publishAutoDismissingNotification(
'success',
`${user.name} has been removed from ${isAbsoluteDelete
? 'all organizations and deleted'
: 'the current organization'}`
)
notify(NOTIFY_CHRONOGRAF_USER_DELETED(user.name, isAbsoluteDelete))
)
} catch (error) {
dispatch(errorThrown(error))
@ -313,12 +309,7 @@ export const deleteOrganizationAsync = organization => async dispatch => {
dispatch(removeOrganization(organization))
try {
await deleteOrganizationAJAX(organization)
dispatch(
publishAutoDismissingNotification(
'success',
`Organization deleted: ${organization.name}`
)
)
dispatch(notify(NOTIFY_CHRONOGRAF_ORG_DELETED(organization.name)))
} catch (error) {
dispatch(errorThrown(error))
dispatch(addOrganization(organization))

View File

@ -18,9 +18,40 @@ import {
import {killQuery as killQueryProxy} from 'shared/apis/metaQuery'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify} from 'shared/actions/notifications'
import {errorThrown} from 'shared/actions/errors'
import {
NOTIFY_DB_USER_CREATED,
NOTIFY_DB_USER_CREATION_FAILED,
NOTIFY_DB_USER_DELETED,
NOTIFY_DB_USER_DELETION_FAILED,
NOTIFY_DB_USER_PERMISSIONS_UPDATED,
NOTIFY_DB_USER_PERMISSIONS_UPDATE_FAILED,
NOTIFY_DB_USER_ROLES_UPDATED,
NOTIFY_DB_USER_ROLES_UPDATE_FAILED,
NOTIFY_DB_USER_PASSWORD_UPDATED,
NOTIFY_DB_USER_PASSWORD_UPDATE_FAILED,
NOTIFY_DATABASE_CREATED,
NOTIFY_DATABASE_CREATION_FAILED,
NOTIFY_DATABASE_DELETED,
NOTIFY_DATABASE_DELETION_FAILED,
NOTIFY_ROLE_CREATED,
NOTIFY_ROLE_CREATION_FAILED,
NOTIFY_ROLE_DELETED,
NOTIFY_ROLE_DELETION_FAILED,
NOTIFY_ROLE_USERS_UPDATED,
NOTIFY_ROLE_USERS_UPDATE_FAILED,
NOTIFY_ROLE_PERMISSIONS_UPDATED,
NOTIFY_ROLE_PERMISSIONS_UPDATE_FAILED,
NOTIFY_RETENTION_POLICY_CREATED,
NOTIFY_RETENTION_POLICY_CREATION_FAILED,
NOTIFY_RETENTION_POLICY_DELETED,
NOTIFY_RETENTION_POLICY_DELETION_FAILED,
NOTIFY_RETENTION_POLICY_UPDATED,
NOTIFY_RETENTION_POLICY_UPDATE_FAILED,
} from 'shared/copy/notifications'
import {REVERT_STATE_DELAY} from 'shared/constants'
import _ from 'lodash'
@ -276,12 +307,12 @@ export const loadDBsAndRPsAsync = url => async dispatch => {
export const createUserAsync = (url, user) => async dispatch => {
try {
const {data} = await createUserAJAX(url, user)
dispatch(
publishAutoDismissingNotification('success', 'User created successfully')
)
dispatch(notify(NOTIFY_DB_USER_CREATED))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(errorThrown(error, `Failed to create user: ${error.data.message}`))
dispatch(
errorThrown(error, NOTIFY_DB_USER_CREATION_FAILED(error.data.message))
)
// undo optimistic update
setTimeout(() => dispatch(deleteUser(user)), REVERT_STATE_DELAY)
}
@ -290,12 +321,12 @@ export const createUserAsync = (url, user) => async dispatch => {
export const createRoleAsync = (url, role) => async dispatch => {
try {
const {data} = await createRoleAJAX(url, role)
dispatch(
publishAutoDismissingNotification('success', 'Role created successfully')
)
dispatch(notify(NOTIFY_ROLE_CREATED))
dispatch(syncRole(role, data))
} catch (error) {
dispatch(errorThrown(error, `Failed to create role: ${error.data.message}`))
dispatch(
errorThrown(error, NOTIFY_ROLE_CREATION_FAILED(error.data.message))
)
// undo optimistic update
setTimeout(() => dispatch(deleteRole(role)), REVERT_STATE_DELAY)
}
@ -305,15 +336,10 @@ export const createDatabaseAsync = (url, database) => async dispatch => {
try {
const {data} = await createDatabaseAJAX(url, database)
dispatch(syncDatabase(database, data))
dispatch(
publishAutoDismissingNotification(
'success',
'Database created successfully'
)
)
dispatch(notify(NOTIFY_DATABASE_CREATED))
} catch (error) {
dispatch(
errorThrown(error, `Failed to create database: ${error.data.message}`)
errorThrown(error, NOTIFY_DATABASE_CREATION_FAILED(error.data.message))
)
// undo optimistic update
setTimeout(() => dispatch(removeDatabase(database)), REVERT_STATE_DELAY)
@ -329,19 +355,11 @@ export const createRetentionPolicyAsync = (
database.links.retentionPolicies,
retentionPolicy
)
dispatch(
publishAutoDismissingNotification(
'success',
'Retention policy created successfully'
)
)
dispatch(notify(NOTIFY_RETENTION_POLICY_CREATED))
dispatch(syncRetentionPolicy(database, retentionPolicy, data))
} catch (error) {
dispatch(
errorThrown(
error,
`Failed to create retention policy: ${error.data.message}`
)
errorThrown(NOTIFY_RETENTION_POLICY_CREATION_FAILED(error.data.message))
)
// undo optimistic update
setTimeout(
@ -360,18 +378,13 @@ export const updateRetentionPolicyAsync = (
dispatch(editRetentionPolicyRequested(database, oldRP, newRP))
const {data} = await updateRetentionPolicyAJAX(oldRP.links.self, newRP)
dispatch(editRetentionPolicyCompleted(database, oldRP, data))
dispatch(
publishAutoDismissingNotification(
'success',
'Retention policy updated successfully'
)
)
dispatch(notify(NOTIFY_RETENTION_POLICY_UPDATED))
} catch (error) {
dispatch(editRetentionPolicyFailed(database, oldRP))
dispatch(
errorThrown(
error,
`Failed to update retention policy: ${error.data.message}`
NOTIFY_RETENTION_POLICY_UPDATE_FAILED(error.data.message)
)
)
}
@ -394,9 +407,11 @@ export const deleteRoleAsync = role => async dispatch => {
dispatch(deleteRole(role))
try {
await deleteRoleAJAX(role.links.self)
dispatch(publishAutoDismissingNotification('success', 'Role deleted'))
dispatch(notify(NOTIFY_ROLE_DELETED(role.name)))
} catch (error) {
dispatch(errorThrown(error, `Failed to delete role: ${error.data.message}`))
dispatch(
errorThrown(error, NOTIFY_ROLE_DELETION_FAILED(error.data.message))
)
}
}
@ -404,9 +419,11 @@ export const deleteUserAsync = user => async dispatch => {
dispatch(deleteUser(user))
try {
await deleteUserAJAX(user.links.self)
dispatch(publishAutoDismissingNotification('success', 'User deleted'))
dispatch(notify(NOTIFY_DB_USER_DELETED(user.name)))
} catch (error) {
dispatch(errorThrown(error, `Failed to delete user: ${error.data.message}`))
dispatch(
errorThrown(error, NOTIFY_DB_USER_DELETION_FAILED(error.data.message))
)
}
}
@ -414,10 +431,10 @@ export const deleteDatabaseAsync = database => async dispatch => {
dispatch(removeDatabase(database))
try {
await deleteDatabaseAJAX(database.links.self)
dispatch(publishAutoDismissingNotification('success', 'Database deleted'))
dispatch(notify(NOTIFY_DATABASE_DELETED(database.name)))
} catch (error) {
dispatch(
errorThrown(error, `Failed to delete database: ${error.data.message}`)
errorThrown(error, NOTIFY_DATABASE_DELETION_FAILED(error.data.message))
)
}
}
@ -429,17 +446,12 @@ export const deleteRetentionPolicyAsync = (
dispatch(removeRetentionPolicy(database, retentionPolicy))
try {
await deleteRetentionPolicyAJAX(retentionPolicy.links.self)
dispatch(
publishAutoDismissingNotification(
'success',
`Retention policy ${retentionPolicy.name} deleted`
)
)
dispatch(notify(NOTIFY_RETENTION_POLICY_DELETED(retentionPolicy.name)))
} catch (error) {
dispatch(
errorThrown(
error,
`Failed to delete retentionPolicy: ${error.data.message}`
NOTIFY_RETENTION_POLICY_DELETION_FAILED(error.data.message)
)
)
}
@ -452,10 +464,12 @@ export const updateRoleUsersAsync = (role, users) => async dispatch => {
users,
role.permissions
)
dispatch(publishAutoDismissingNotification('success', 'Role users updated'))
dispatch(notify(NOTIFY_ROLE_USERS_UPDATED))
dispatch(syncRole(role, data))
} catch (error) {
dispatch(errorThrown(error, `Failed to update role: ${error.data.message}`))
dispatch(
errorThrown(error, NOTIFY_ROLE_USERS_UPDATE_FAILED(error.data.message))
)
}
}
@ -469,13 +483,14 @@ export const updateRolePermissionsAsync = (
role.users,
permissions
)
dispatch(
publishAutoDismissingNotification('success', 'Role permissions updated')
)
dispatch(notify(NOTIFY_ROLE_PERMISSIONS_UPDATED))
dispatch(syncRole(role, data))
} catch (error) {
dispatch(
errorThrown(error, `Failed to update role: ${error.data.message}`)
errorThrown(
error,
NOTIFY_ROLE_PERMISSIONS_UPDATE_FAILED(error.data.message)
)
)
}
}
@ -486,13 +501,14 @@ export const updateUserPermissionsAsync = (
) => async dispatch => {
try {
const {data} = await updateUserAJAX(user.links.self, {permissions})
dispatch(
publishAutoDismissingNotification('success', 'User permissions updated')
)
dispatch(notify(NOTIFY_DB_USER_PERMISSIONS_UPDATED))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(
errorThrown(error, `Failed to update user: ${error.data.message}`)
errorThrown(
error,
NOTIFY_DB_USER_PERMISSIONS_UPDATE_FAILED(error.data.message)
)
)
}
}
@ -500,11 +516,11 @@ export const updateUserPermissionsAsync = (
export const updateUserRolesAsync = (user, roles) => async dispatch => {
try {
const {data} = await updateUserAJAX(user.links.self, {roles})
dispatch(publishAutoDismissingNotification('success', 'User roles updated'))
dispatch(notify(NOTIFY_DB_USER_ROLES_UPDATED))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(
errorThrown(error, `Failed to update user: ${error.data.message}`)
errorThrown(error, NOTIFY_DB_USER_ROLES_UPDATE_FAILED(error.data.message))
)
}
}
@ -512,13 +528,14 @@ export const updateUserRolesAsync = (user, roles) => async dispatch => {
export const updateUserPasswordAsync = (user, password) => async dispatch => {
try {
const {data} = await updateUserAJAX(user.links.self, {password})
dispatch(
publishAutoDismissingNotification('success', 'User password updated')
)
dispatch(notify(NOTIFY_DB_USER_PASSWORD_UPDATED))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(
errorThrown(error, `Failed to update user: ${error.data.message}`)
errorThrown(
error,
NOTIFY_DB_USER_PASSWORD_UPDATE_FAILED(error.data.message)
)
)
}
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import UsersTable from 'src/admin/components/UsersTable'
import RolesTable from 'src/admin/components/RolesTable'

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import OnClickOutside from 'shared/components/OnClickOutside'
import ConfirmButtons from 'shared/components/ConfirmButtons'
@ -9,18 +10,13 @@ class ChangePassRow extends Component {
this.state = {
showForm: false,
}
this.showForm = ::this.showForm
this.handleCancel = ::this.handleCancel
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
this.handleSubmit = ::this.handleSubmit
}
showForm() {
showForm = () => {
this.setState({showForm: true})
}
handleCancel() {
handleCancel = () => {
this.setState({showForm: false})
}
@ -28,12 +24,12 @@ class ChangePassRow extends Component {
this.setState({showForm: false})
}
handleSubmit(user) {
handleSubmit = user => {
this.props.onApply(user)
this.setState({showForm: false})
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.handleSubmit(user)
@ -41,7 +37,7 @@ class ChangePassRow extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -1,10 +1,10 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import DatabaseTable from 'src/admin/components/DatabaseTable'
const DatabaseManager = ({
databases,
notify,
isRFDisplayed,
isAddDBDisabled,
addDatabase,
@ -25,8 +25,8 @@ const DatabaseManager = ({
onDeleteRetentionPolicy,
}) => {
return (
<div className="panel panel-default">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel panel-solid">
<div className="panel-heading">
<h2 className="panel-title">
{databases.length === 1
? '1 Database'
@ -45,7 +45,6 @@ const DatabaseManager = ({
<DatabaseTable
key={db.links.self}
database={db}
notify={notify}
isRFDisplayed={isRFDisplayed}
onEditDatabase={onEditDatabase}
onKeyDownDatabase={onKeyDownDatabase}
@ -73,7 +72,6 @@ const {arrayOf, bool, func, shape} = PropTypes
DatabaseManager.propTypes = {
databases: arrayOf(shape()),
notify: func,
addDatabase: func,
isRFDisplayed: bool,
isAddDBDisabled: bool,

View File

@ -1,9 +1,16 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import onClickOutside from 'react-onclickoutside'
import {notify as notifyAction} from 'shared/actions/notifications'
import {formatRPDuration} from 'utils/formatting'
import YesNoButtons from 'shared/components/YesNoButtons'
import {DATABASE_TABLE} from 'src/admin/constants/tableSizing'
import {NOTIFY_RETENTION_POLICY_CANT_HAVE_EMPTY_FIELDS} from 'shared/copy/notifications'
class DatabaseRow extends Component {
constructor(props) {
@ -109,7 +116,7 @@ class DatabaseRow extends Component {
const replication = isRFDisplayed ? +this.replication.value.trim() : 1
if (!duration || (isRFDisplayed && !replication)) {
notify('error', 'Fields cannot be empty')
notify(NOTIFY_RETENTION_POLICY_CANT_HAVE_EMPTY_FIELDS)
return
}
@ -260,8 +267,12 @@ DatabaseRow.propTypes = {
onCreate: func,
onUpdate: func,
onDelete: func,
notify: func,
notify: func.isRequired,
isRFDisplayed: bool,
}
export default onClickOutside(DatabaseRow)
const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(null, mapDispatchToProps)(onClickOutside(DatabaseRow))

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
@ -11,7 +12,6 @@ const {func, shape, bool} = PropTypes
const DatabaseTable = ({
database,
notify,
isRFDisplayed,
onEditDatabase,
onKeyDownDatabase,
@ -35,7 +35,6 @@ const DatabaseTable = ({
>
<DatabaseTableHeader
database={database}
notify={notify}
onEdit={onEditDatabase}
onCancel={onCancelDatabase}
onDelete={onDeleteDatabase}
@ -73,7 +72,6 @@ const DatabaseTable = ({
return (
<DatabaseRow
key={rp.links.self}
notify={notify}
database={database}
retentionPolicy={rp}
onCreate={onCreateRetentionPolicy}
@ -95,7 +93,6 @@ const DatabaseTable = ({
DatabaseTable.propTypes = {
onEditDatabase: func,
database: shape(),
notify: func,
isRFDisplayed: bool,
isAddRPDisabled: bool,
onKeyDownDatabase: func,

View File

@ -1,5 +1,12 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {notify as notifyAction} from 'shared/actions/notifications'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import {NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED} from 'shared/copy/notifications'
const DatabaseTableHeader = ({
database,
@ -53,7 +60,7 @@ const Header = ({
onDatabaseDeleteConfirm,
}) => {
const buttons = (
<div className="text-right db-manager-header--actions">
<div className="db-manager-header--actions text-right">
<button
className="btn btn-xs btn-primary"
disabled={isAddRPDisabled}
@ -74,7 +81,7 @@ const Header = ({
const onConfirm = db => {
if (database.deleteCode !== `DELETE ${database.name}`) {
return notify('error', `Type DELETE ${database.name} to confirm`)
return notify(NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED(database.name))
}
onDelete(db)
@ -134,7 +141,7 @@ const {func, shape, bool} = PropTypes
DatabaseTableHeader.propTypes = {
onEdit: func,
notify: func,
notify: func.isRequired,
database: shape(),
onKeyDown: func,
onCancel: func,
@ -148,7 +155,7 @@ DatabaseTableHeader.propTypes = {
}
Header.propTypes = {
notify: func,
notify: func.isRequired,
onConfirm: func,
onCancel: func,
onDelete: func,
@ -168,4 +175,8 @@ EditHeader.propTypes = {
isRFDisplayed: bool,
}
export default DatabaseTableHeader
const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(null, mapDispatchToProps)(DatabaseTableHeader)

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
const EmptyRow = ({tableName}) =>
<tr className="table-empty-state">

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class FilterBar extends Component {
constructor(props) {
@ -26,8 +27,8 @@ class FilterBar extends Component {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
})
return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="users__search-widget input-group admin__search-widget">
<div className="panel-heading">
<div className="search-widget" style={{width: '300px'}}>
<input
type="text"
className="form-control input-sm"
@ -35,9 +36,7 @@ class FilterBar extends Component {
value={this.state.filterText}
onChange={this.handleText}
/>
<div className="input-group-addon">
<span className="icon search" aria-hidden="true" />
</div>
<span className="icon search" />
</div>
<button
className="btn btn-sm btn-primary"

View File

@ -1,11 +1,12 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import QueryRow from 'src/admin/components/QueryRow'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
const QueriesTable = ({queries, onKillQuery}) =>
<div>
<div className="panel panel-default">
<div className="panel panel-solid">
<div className="panel-body">
<table className="table v-center admin-table table-highlight">
<thead>

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
@ -7,24 +8,20 @@ class QueryRow extends Component {
constructor(props) {
super(props)
this.handleInitiateKill = ::this.handleInitiateKill
this.handleFinishHim = ::this.handleFinishHim
this.handleShowMercy = ::this.handleShowMercy
this.state = {
confirmingKill: false,
}
}
handleInitiateKill() {
handleInitiateKill = () => {
this.setState({confirmingKill: true})
}
handleFinishHim() {
handleFinishHim = () => {
this.props.onKill(this.props.query.id)
}
handleShowMercy() {
handleShowMercy = () => {
this.setState({confirmingKill: false})
}

View File

@ -1,16 +1,14 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
class RoleEditingRow extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(role) {
handleKeyPress = role => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(role)
@ -18,7 +16,7 @@ class RoleEditingRow extends Component {
}
}
handleEdit(role) {
handleEdit = role => {
return e => {
this.props.onEdit(role, {[e.target.name]: e.target.value})
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import RoleRow from 'src/admin/components/RoleRow'
import EmptyRow from 'src/admin/components/EmptyRow'
import FilterBar from 'src/admin/components/FilterBar'
@ -17,7 +18,7 @@ const RolesTable = ({
onUpdateRoleUsers,
onUpdateRolePermissions,
}) =>
<div className="panel panel-default">
<div className="panel panel-solid">
<FilterBar
type="roles"
onFilter={onFilter}

View File

@ -1,16 +1,14 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserEditName extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
@ -18,7 +16,7 @@ class UserEditName extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -1,16 +1,10 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserNewPassword extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
@ -18,7 +12,7 @@ class UserNewPassword extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import UserRow from 'src/admin/components/UserRow'
import EmptyRow from 'src/admin/components/EmptyRow'
@ -20,7 +21,7 @@ const UsersTable = ({
onUpdateRoles,
onUpdatePassword,
}) =>
<div className="panel panel-default">
<div className="panel panel-solid">
<FilterBar
type="users"
onFilter={onFilter}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {
isUserAuthorized,

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'node-uuid'
import uuid from 'uuid'
import AllUsersTableHeader from 'src/admin/components/chronograf/AllUsersTableHeader'
import AllUsersTableRowNew from 'src/admin/components/chronograf/AllUsersTableRowNew'
@ -15,6 +16,11 @@ const {
colActions,
} = ALL_USERS_TABLE
import {
NOTIFY_CHRONOGRAF_USER_ADDED_TO_ORG,
NOTIFY_CHRONOGRAF_USER_REMOVED_FROM_ORG,
} from 'shared/copy/notifications'
class AllUsersTable extends Component {
constructor(props) {
super(props)
@ -46,7 +52,7 @@ class AllUsersTable extends Component {
this.props.onUpdateUserRoles(
user,
newRoles,
`${user.name} has been added to ${organization.name}`
NOTIFY_CHRONOGRAF_USER_ADDED_TO_ORG(user.name, organization.name)
)
}
@ -60,7 +66,7 @@ class AllUsersTable extends Component {
this.props.onUpdateUserRoles(
user,
newRoles,
`${user.name} has been removed from ${name}`
NOTIFY_CHRONOGRAF_USER_REMOVED_FROM_ORG(user.name, name)
)
}
@ -83,7 +89,6 @@ class AllUsersTable extends Component {
onCreateUser,
authConfig,
meID,
notify,
onDeleteUser,
isLoading,
} = this.props
@ -91,7 +96,7 @@ class AllUsersTable extends Component {
const {isCreatingUser} = this.state
if (isLoading) {
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<div className="panel-body">
<div className="page-spinner" />
</div>
@ -99,7 +104,7 @@ class AllUsersTable extends Component {
)
}
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<AllUsersTableHeader
numUsers={users.length}
numOrganizations={organizations.length}
@ -153,7 +158,6 @@ class AllUsersTable extends Component {
organizations={organizations}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
notify={notify}
/>
: null}
</tbody>
@ -208,7 +212,6 @@ AllUsersTable.propTypes = {
superAdminNewUsers: bool,
}),
meID: string.isRequired,
notify: func.isRequired,
isLoading: bool.isRequired,
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import SlideToggle from 'shared/components/SlideToggle'
@ -17,7 +18,7 @@ const AllUsersTableHeader = ({
: 's'}`
return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel-heading">
<h2 className="panel-title">
{numUsersString} across {numOrganizationsString}
</h2>

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import Tags from 'shared/components/Tags'
import SlideToggle from 'shared/components/SlideToggle'

View File

@ -1,7 +1,12 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {notify as notifyAction} from 'shared/actions/notifications'
import Dropdown from 'shared/components/Dropdown'
import {NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER} from 'shared/copy/notifications'
import {ALL_USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const {
colOrganizations,
@ -79,8 +84,7 @@ class AllUsersTableRowNew extends Component {
if (e.key === 'Enter') {
if (preventCreate) {
return this.props.notify(
'warning',
'User must have a name and provider'
NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER
)
}
this.handleConfirmCreateUser()
@ -180,4 +184,8 @@ AllUsersTableRowNew.propTypes = {
notify: func.isRequired,
}
export default AllUsersTableRowNew
const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(null, mapDispatchToProps)(AllUsersTableRowNew)

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'node-uuid'
import uuid from 'uuid'
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
@ -45,7 +46,7 @@ class OrganizationsTable extends Component {
if (!organizations.length) {
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<div className="panel-body">
<div className="page-spinner" />
</div>
@ -53,8 +54,8 @@ class OrganizationsTable extends Component {
)
}
return (
<div className="panel panel-default">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel panel-solid">
<div className="panel-heading">
<h2 className="panel-title">
{tableTitle}
</h2>

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router'
@ -94,7 +95,7 @@ class OrganizationsTableRow extends Component {
<InputClickToEdit
value={organization.name}
wrapperClass="fancytable--td orgs-table--name"
onUpdate={this.handleUpdateOrgName}
onBlur={this.handleUpdateOrgName}
/>
<div className={defaultRoleClassName}>
<Dropdown
@ -111,6 +112,7 @@ class OrganizationsTableRow extends Component {
onConfirm={this.handleDeleteOrg}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmLeft={true}
confirmTitle="Delete"
/>
: <OrganizationsTableRowDeleteButton
organization={organization}

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown'

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow'
import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew'
@ -43,7 +44,7 @@ class ProvidersTable extends Component {
if (isLoading) {
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<div className="panel-body">
<div className="page-spinner" />
</div>
@ -52,8 +53,8 @@ class ProvidersTable extends Component {
}
return (
<div className="panel panel-default">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel panel-solid">
<div className="panel-heading">
<h2 className="panel-title">
{tableTitle}
</h2>

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown'
@ -80,14 +81,14 @@ class ProvidersTableRow extends Component {
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider}
onBlur={this.handleChangeProvider}
disabled={isDefaultMapping}
tabIndex={rowIndex}
/>
<InputClickToEdit
value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg}
onBlur={this.handleChangeProviderOrg}
disabled={isDefaultMapping}
tabIndex={rowIndex}
/>
@ -109,6 +110,7 @@ class ProvidersTableRow extends Component {
onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteMap}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmTitle="Delete"
/>
: <button
className="btn btn-sm btn-default btn-square"

View File

@ -1,10 +1,34 @@
import React, {Component, PropTypes} from 'react'
import React, {PureComponent} from 'react'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown'
import InputClickToEdit from 'shared/components/InputClickToEdit'
import ConfirmButtons from 'src/shared/components/ConfirmButtons'
import Dropdown from 'src/shared/components/Dropdown'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
class ProvidersTableRowNew extends Component {
type Organization = {
id: string
name: string
}
type Scheme = {
text: string
}
interface Props {
organizations: Organization[]
schemes?: Scheme[]
rowIndex?: number
onCreate: (state: State) => void
onCancel: () => void
}
interface State {
scheme: string
provider: string
providerOrganization: string
organizationId: string
}
class ProvidersTableRowNew extends PureComponent<Props, State> {
constructor(props) {
super(props)
@ -14,25 +38,31 @@ class ProvidersTableRowNew extends Component {
providerOrganization: null,
organizationId: 'default',
}
this.handleChooseScheme = this.handleChooseScheme.bind(this)
this.handleChangeProvider = this.handleChangeProvider.bind(this)
this.handleChangeProviderOrg = this.handleChangeProviderOrg.bind(this)
this.handleChooseOrganization = this.handleChooseOrganization.bind(this)
this.handleSaveNewMapping = this.handleSaveNewMapping.bind(this)
}
handleChooseScheme = scheme => {
handleChooseScheme(scheme: Scheme) {
this.setState({scheme: scheme.text})
}
handleChangeProvider = provider => {
handleChangeProvider(provider: string) {
this.setState({provider})
}
handleChangeProviderOrg = providerOrganization => {
handleChangeProviderOrg(providerOrganization: string) {
this.setState({providerOrganization})
}
handleChooseOrganization = org => {
handleChooseOrganization(org: Organization) {
this.setState({organizationId: org.id})
}
handleSaveNewMapping = () => {
handleSaveNewMapping() {
const {onCreate} = this.props
onCreate(this.state)
}
@ -62,14 +92,16 @@ class ProvidersTableRowNew extends Component {
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider}
onChange={this.handleChangeProvider}
onBlur={this.handleChangeProvider}
tabIndex={rowIndex}
placeholder="google"
/>
<InputClickToEdit
value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg}
onChange={this.handleChangeProviderOrg}
onBlur={this.handleChangeProviderOrg}
tabIndex={rowIndex}
placeholder="*"
/>
@ -94,23 +126,4 @@ class ProvidersTableRowNew extends Component {
}
}
const {arrayOf, func, number, shape, string} = PropTypes
ProvidersTableRowNew.propTypes = {
organizations: arrayOf(
shape({
id: string.isRequired,
name: string.isRequired,
})
).isRequired,
schemes: arrayOf(
shape({
text: string.isRequired,
})
),
rowIndex: number,
onCreate: func.isRequired,
onCancel: func.isRequired,
}
export default ProvidersTableRowNew

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'node-uuid'
import uuid from 'uuid'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'
@ -34,21 +35,14 @@ class UsersTable extends Component {
}
render() {
const {
organization,
users,
onCreateUser,
meID,
notify,
isLoading,
} = this.props
const {organization, users, onCreateUser, meID, isLoading} = this.props
const {isCreatingUser} = this.state
const {colRole, colProvider, colScheme, colActions} = USERS_TABLE
if (isLoading) {
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<div className="panel-body">
<div className="page-spinner" />
</div>
@ -56,7 +50,7 @@ class UsersTable extends Component {
)
}
return (
<div className="panel panel-default">
<div className="panel panel-solid">
<UsersTableHeader
numUsers={users.length}
onClickCreateUser={this.handleClickCreateUser}
@ -82,7 +76,6 @@ class UsersTable extends Component {
organization={organization}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
notify={notify}
/>
: null}
{users.length
@ -137,7 +130,6 @@ UsersTable.propTypes = {
onUpdateUserRole: func.isRequired,
onDeleteUser: func.isRequired,
meID: string.isRequired,
notify: func.isRequired,
isLoading: bool.isRequired,
}

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class UsersTableHeader extends Component {
constructor(props) {
@ -16,7 +17,7 @@ class UsersTableHeader extends Component {
const panelTitle = numUsers === 1 ? `${numUsers} User` : `${numUsers} Users`
return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel-heading">
<h2 className="panel-title">
{panelTitle} in <em>{organization.name}</em>
</h2>

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'

View File

@ -1,7 +1,13 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {notify as notifyAction} from 'shared/actions/notifications'
import Dropdown from 'shared/components/Dropdown'
import {NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER} from 'shared/copy/notifications'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
@ -60,8 +66,7 @@ class UsersTableRowNew extends Component {
if (e.key === 'Enter') {
if (preventCreate) {
return this.props.notify(
'warning',
'User must have a name and provider'
NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER
)
}
this.handleConfirmCreateUser()
@ -147,4 +152,8 @@ UsersTableRowNew.propTypes = {
notify: func.isRequired,
}
export default UsersTableRowNew
const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(null, mapDispatchToProps)(UsersTableRowNew)

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {
@ -28,7 +29,12 @@ import AdminTabs from 'src/admin/components/AdminTabs'
import SourceIndicator from 'shared/components/SourceIndicator'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify as notifyAction} from 'shared/actions/notifications'
import {
NOTIFY_ROLE_NAME_INVALID,
NOTIFY_DB_USER_NAME_PASSWORD_INVALID,
} from 'shared/copy/notifications'
const isValidUser = user => {
const minLen = 3
@ -74,7 +80,7 @@ class AdminInfluxDBPage extends Component {
handleSaveUser = async user => {
const {notify} = this.props
if (!isValidUser(user)) {
notify('error', 'Username and/or password too short')
notify(NOTIFY_DB_USER_NAME_PASSWORD_INVALID)
return
}
if (user.isNew) {
@ -87,7 +93,7 @@ class AdminInfluxDBPage extends Component {
handleSaveRole = async role => {
const {notify} = this.props
if (!isValidRole(role)) {
notify('error', 'Role name too short')
notify(NOTIFY_ROLE_NAME_INVALID)
return
}
if (role.isNew) {
@ -228,7 +234,7 @@ AdminInfluxDBPage.propTypes = {
updateUserPermissions: func,
updateUserRoles: func,
updateUserPassword: func,
notify: func,
notify: func.isRequired,
}
const mapStateToProps = ({adminInfluxDB: {users, roles, permissions}}) => ({
@ -264,7 +270,7 @@ const mapDispatchToProps = dispatch => ({
),
updateUserRoles: bindActionCreators(updateUserRolesAsync, dispatch),
updateUserPassword: bindActionCreators(updateUserPasswordAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AdminInfluxDBPage)

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
@ -6,7 +7,13 @@ import _ from 'lodash'
import DatabaseManager from 'src/admin/components/DatabaseManager'
import * as adminActionCreators from 'src/admin/actions/influxdb'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify as notifyAction} from 'shared/actions/notifications'
import {
NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED,
NOTIFY_DATABASE_NAME_ALREADY_EXISTS,
NOTIFY_DATABASE_NAME_INVALID,
} from 'shared/copy/notifications'
class DatabaseManagerPage extends Component {
constructor(props) {
@ -34,11 +41,11 @@ class DatabaseManagerPage extends Component {
handleCreateDatabase = database => {
const {actions, notify, source, databases} = this.props
if (!database.name) {
return notify('error', 'Database name cannot be blank')
return notify(NOTIFY_DATABASE_NAME_INVALID)
}
if (_.findIndex(databases, {name: database.name}, 1) !== -1) {
return notify('error', 'A database by this name already exists')
return notify(NOTIFY_DATABASE_NAME_ALREADY_EXISTS)
}
actions.createDatabaseAsync(source.links.databases, database)
@ -59,11 +66,11 @@ class DatabaseManagerPage extends Component {
if (key === 'Enter') {
if (!database.name) {
return notify('error', 'Database name cannot be blank')
return notify(NOTIFY_DATABASE_NAME_INVALID)
}
if (_.findIndex(databases, {name: database.name}, 1) !== -1) {
return notify('error', 'A database by this name already exists')
return notify(NOTIFY_DATABASE_NAME_ALREADY_EXISTS)
}
actions.createDatabaseAsync(source.links.databases, database)
@ -80,7 +87,9 @@ class DatabaseManagerPage extends Component {
if (key === 'Enter') {
if (database.deleteCode !== `DELETE ${database.name}`) {
return notify('error', `Please type DELETE ${database.name} to confirm`)
return notify(
NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED(database.name)
)
}
return actions.deleteDatabaseAsync(database)
@ -152,7 +161,7 @@ DatabaseManagerPage.propTypes = {
removeRetentionPolicy: func,
deleteRetentionPolicyAsync: func,
}),
notify: func,
notify: func.isRequired,
}
const mapStateToProps = ({adminInfluxDB: {databases, retentionPolicies}}) => ({
@ -162,7 +171,7 @@ const mapStateToProps = ({adminInfluxDB: {databases, retentionPolicies}}) => ({
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(DatabaseManagerPage)

View File

@ -1,9 +1,10 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify as notifyAction} from 'shared/actions/notifications'
import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable'
@ -95,7 +96,7 @@ const mapStateToProps = ({
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(ProvidersPage)

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
@ -11,21 +12,17 @@ import QueriesTable from 'src/admin/components/QueriesTable'
import showDatabasesParser from 'shared/parsing/showDatabases'
import showQueriesParser from 'shared/parsing/showQueries'
import {TIMES} from 'src/admin/constants'
import {NOTIFY_QUERIES_ERROR} from 'shared/copy/notifications'
import {
loadQueries as loadQueriesAction,
setQueryToKill as setQueryToKillAction,
killQueryAsync,
} from 'src/admin/actions/influxdb'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify as notifyAction} from 'shared/actions/notifications'
class QueriesPage extends Component {
constructor(props) {
super(props)
this.updateQueries = ::this.updateQueries
this.handleKillQuery = ::this.handleKillQuery
}
componentDidMount() {
this.updateQueries()
const updateInterval = 5000
@ -42,12 +39,12 @@ class QueriesPage extends Component {
return <QueriesTable queries={queries} onKillQuery={this.handleKillQuery} />
}
updateQueries() {
updateQueries = () => {
const {source, notify, loadQueries} = this.props
showDatabases(source.links.proxy).then(resp => {
const {databases, errors} = showDatabasesParser(resp.data)
if (errors.length) {
errors.forEach(message => notify('error', message))
errors.forEach(message => notify(NOTIFY_QUERIES_ERROR(message)))
return
}
@ -58,7 +55,9 @@ class QueriesPage extends Component {
queryResponses.forEach(queryResponse => {
const result = showQueriesParser(queryResponse.data)
if (result.errors.length) {
result.errors.forEach(message => notify('error', message))
result.errors.forEach(message =>
notify(NOTIFY_QUERIES_ERROR(message))
)
}
allQueries.push(...result.queries)
@ -78,7 +77,7 @@ class QueriesPage extends Component {
})
}
handleKillQuery(id) {
handleKillQuery = id => {
const {source, killQuery} = this.props
killQuery(source.links.proxy, id)
}
@ -97,7 +96,7 @@ QueriesPage.propTypes = {
queryIDToKill: string,
setQueryToKill: func,
killQuery: func,
notify: func,
notify: func.isRequired,
}
const mapStateToProps = ({adminInfluxDB: {queries, queryIDToKill}}) => ({
@ -109,7 +108,7 @@ const mapDispatchToProps = dispatch => ({
loadQueries: bindActionCreators(loadQueriesAction, dispatch),
setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch),
killQuery: bindActionCreators(killQueryAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(QueriesPage)

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import AdminTabs from 'src/admin/components/chronograf/AdminTabs'

View File

@ -1,14 +1,44 @@
import React, {Component, PropTypes} from 'react'
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import * as configActionCreators from 'shared/actions/config'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import * as configActionCreators from 'src/shared/actions/config'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable'
import {AuthLinks, User, Role, Organization} from 'src/types'
class AllUsersPage extends Component {
interface Props {
notify: () => void
links: AuthLinks
meID: string
users: User[]
organizations: Organization[]
actionsAdmin: {
loadUsersAsync: (link: string) => void
loadOrganizationsAsync: (link: string) => void
createUserAsync: (link: string, user: User) => void
updateUserAsync: (user: User, updatedUser: User, message: string) => void
deleteUserAsync: (
user: User,
deleteObj: {isAbsoluteDelete: boolean}
) => void
}
actionsConfig: {
getAuthConfigAsync: (link: string) => void
updateAuthConfigAsync: () => void
}
authConfig: {
superAdminNewUsers: boolean
}
}
interface State {
isLoading: boolean
}
export class AllUsersPage extends PureComponent<Props, State> {
constructor(props) {
super(props)
@ -22,32 +52,6 @@ class AllUsersPage extends Component {
getAuthConfigAsync(links.config.auth)
}
handleCreateUser = user => {
const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user)
}
handleUpdateUserRoles = (user, roles, successMessage) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, roles}
updateUserAsync(user, updatedUser, successMessage)
}
handleUpdateUserSuperAdmin = (user, superAdmin) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(
user,
updatedUser,
`${user.name}'s SuperAdmin status has been updated`
)
}
handleDeleteUser = user => {
const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true})
}
async componentWillMount() {
const {
links,
@ -64,65 +68,66 @@ class AllUsersPage extends Component {
this.setState({isLoading: false})
}
handleCreateUser = (user: User) => {
const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user)
}
handleUpdateUserRoles = (
user: User,
roles: Role[],
successMessage: string
) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, roles}
updateUserAsync(user, updatedUser, successMessage)
}
handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(
user,
updatedUser,
`${user.name}'s SuperAdmin status has been updated`
)
}
handleDeleteUser = (user: User) => {
const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true})
}
render() {
const {
organizations,
meID,
users,
authConfig,
actionsConfig,
links,
notify,
authConfig,
actionsConfig,
organizations,
} = this.props
return (
<AllUsersTable
meID={meID}
users={users}
links={links}
notify={notify}
authConfig={authConfig}
actionsConfig={actionsConfig}
organizations={organizations}
isLoading={this.state.isLoading}
onDeleteUser={this.handleDeleteUser}
onCreateUser={this.handleCreateUser}
onUpdateUserRoles={this.handleUpdateUserRoles}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin}
onDeleteUser={this.handleDeleteUser}
links={links}
authConfig={authConfig}
actionsConfig={actionsConfig}
notify={notify}
isLoading={this.state.isLoading}
/>
)
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
AllUsersPage.propTypes = {
links: shape({
users: string.isRequired,
config: shape({
auth: string.isRequired,
}).isRequired,
}),
meID: string.isRequired,
users: arrayOf(shape),
organizations: arrayOf(shape),
actionsAdmin: shape({
loadUsersAsync: func.isRequired,
loadOrganizationsAsync: func.isRequired,
createUserAsync: func.isRequired,
updateUserAsync: func.isRequired,
deleteUserAsync: func.isRequired,
}),
actionsConfig: shape({
getAuthConfigAsync: func.isRequired,
updateAuthConfigAsync: func.isRequired,
}),
authConfig: shape({
superAdminNewUsers: bool,
}),
notify: func.isRequired,
}
const mapStateToProps = ({
links,
adminChronograf: {organizations, users},
@ -137,7 +142,7 @@ const mapStateToProps = ({
const mapDispatchToProps = dispatch => ({
actionsAdmin: bindActionCreators(adminChronografActionCreators, dispatch),
actionsConfig: bindActionCreators(configActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AllUsersPage)

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

View File

@ -1,9 +1,10 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify as notifyAction} from 'shared/actions/notifications'
import UsersTable from 'src/admin/components/chronograf/UsersTable'
@ -115,7 +116,7 @@ const mapStateToProps = ({links, adminChronograf: {organizations, users}}) => ({
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(UsersPage)

View File

@ -5,7 +5,7 @@ import {
NEW_DEFAULT_DATABASE,
NEW_EMPTY_RP,
} from 'src/admin/constants'
import uuid from 'node-uuid'
import uuid from 'uuid'
const initialState = {
users: null,

View File

@ -1,9 +1,10 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import {Link} from 'react-router'
import uuid from 'node-uuid'
import uuid from 'uuid'
import InfiniteScroll from 'shared/components/InfiniteScroll'
@ -220,8 +221,8 @@ class AlertsTable extends Component {
</button>
: null}
</div>
: <div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
: <div className="panel">
<div className="panel-heading">
<h2 className="panel-title">
{this.props.alerts.length} Alerts
</h2>
@ -243,9 +244,6 @@ class SearchBar extends Component {
this.state = {
searchTerm: '',
}
this.handleSearch = ::this.handleSearch
this.handleChange = ::this.handleChange
}
componentWillMount() {
@ -253,27 +251,25 @@ class SearchBar extends Component {
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
}
handleSearch() {
handleSearch = () => {
this.props.onSearch(this.state.searchTerm)
}
handleChange(e) {
handleChange = e => {
this.setState({searchTerm: e.target.value}, this.handleSearch)
}
render() {
return (
<div className="users__search-widget input-group">
<div className="search-widget" style={{width: '260px'}}>
<input
type="text"
className="form-control"
className="form-control input-sm"
placeholder="Filter Alerts..."
onChange={this.handleChange}
value={this.state.searchTerm}
/>
<div className="input-group-addon">
<span className="icon search" />
</div>
<span className="icon search" />
</div>
)
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import SourceIndicator from 'shared/components/SourceIndicator'
import AlertsTable from 'src/alerts/components/AlertsTable'
@ -11,7 +12,7 @@ import AJAX from 'utils/ajax'
import _ from 'lodash'
import moment from 'moment'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
class AlertsApp extends Component {
constructor(props) {
@ -35,11 +36,6 @@ class AlertsApp extends Component {
limitMultiplier: 1, // only used if AlertsApp receives a limit prop
isAlertsMaxedOut: false, // only used if AlertsApp receives a limit prop
}
this.fetchAlerts = ::this.fetchAlerts
this.renderSubComponents = ::this.renderSubComponents
this.handleGetMoreAlerts = ::this.handleGetMoreAlerts
this.handleApplyTime = ::this.handleApplyTime
}
// TODO: show a loading screen until we figure out if there is a kapacitor and fetch the alerts
@ -65,7 +61,7 @@ class AlertsApp extends Component {
}
}
fetchAlerts() {
fetchAlerts = () => {
getAlerts(
this.props.source.links.proxy,
this.state.timeRange,
@ -112,13 +108,13 @@ class AlertsApp extends Component {
})
}
handleGetMoreAlerts() {
handleGetMoreAlerts = () => {
this.setState({limitMultiplier: this.state.limitMultiplier + 1}, () => {
this.fetchAlerts(this.state.limitMultiplier)
})
}
renderSubComponents() {
renderSubComponents = () => {
const {source, isWidget, limit} = this.props
const {isAlertsMaxedOut, alerts} = this.state
@ -135,7 +131,7 @@ class AlertsApp extends Component {
: <NoKapacitorError source={source} />
}
handleApplyTime(timeRange) {
handleApplyTime = timeRange => {
this.setState({timeRange})
}

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {withRouter} from 'react-router'

View File

@ -1,5 +1,6 @@
/* global VERSION */
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import Notifications from 'shared/components/Notifications'

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router'

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {isUserAuthorized, VIEWER_ROLE} from 'src/auth/Authorized'

View File

@ -23,17 +23,17 @@ export const renameCell = cellName => ({
},
})
export const updateSingleStatColors = singleStatColors => ({
type: 'UPDATE_SINGLE_STAT_COLORS',
export const updateThresholdsListColors = thresholdsListColors => ({
type: 'UPDATE_THRESHOLDS_LIST_COLORS',
payload: {
singleStatColors,
thresholdsListColors,
},
})
export const updateSingleStatType = singleStatType => ({
type: 'UPDATE_SINGLE_STAT_TYPE',
export const updateThresholdsListType = thresholdsListType => ({
type: 'UPDATE_THRESHOLDS_LIST_TYPE',
payload: {
singleStatType,
thresholdsListType,
},
})
@ -50,3 +50,10 @@ export const updateAxes = axes => ({
axes,
},
})
export const updateTableOptions = tableOptions => ({
type: 'UPDATE_TABLE_OPTIONS',
payload: {
tableOptions,
},
})

View File

@ -8,10 +8,14 @@ import {
runTemplateVariableQuery,
} from 'src/dashboards/apis'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {notify} from 'shared/actions/notifications'
import {errorThrown} from 'shared/actions/errors'
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants'
import {
NOTIFY_DASHBOARD_DELETED,
NOTIFY_DASHBOARD_DELETE_FAILED,
} from 'shared/copy/notifications'
import {
TEMPLATE_VARIABLE_SELECTED,
@ -257,15 +261,13 @@ export const deleteDashboardAsync = dashboard => async dispatch => {
dispatch(deleteDashboard(dashboard))
try {
await deleteDashboardAJAX(dashboard)
dispatch(
publishAutoDismissingNotification(
'success',
'Dashboard deleted successfully.'
)
)
dispatch(notify(NOTIFY_DASHBOARD_DELETED(dashboard.name)))
} catch (error) {
dispatch(
errorThrown(error, `Failed to delete dashboard: ${error.data.message}.`)
errorThrown(
error,
NOTIFY_DASHBOARD_DELETE_FAILED(dashboard.name, error.data.message)
)
)
dispatch(deleteDashboardFailed(dashboard))
}

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
@ -7,12 +8,15 @@ import Input from 'src/dashboards/components/DisplayOptionsInput'
import {Tabber, Tab} from 'src/dashboards/components/Tabber'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DISPLAY_OPTIONS, TOOLTIP_CONTENT} from 'src/dashboards/constants'
import {
AXES_SCALE_OPTIONS,
TOOLTIP_Y_VALUE_FORMAT,
} from 'src/dashboards/constants/cellEditor'
import {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay'
const {LINEAR, LOG, BASE_2, BASE_10} = DISPLAY_OPTIONS
const {LINEAR, LOG, BASE_2, BASE_10} = AXES_SCALE_OPTIONS
const getInputMin = scale => (scale === LOG ? '0' : null)
class AxesOptions extends Component {
@ -90,7 +94,7 @@ class AxesOptions extends Component {
<h5 className="display-options--header">
{menuOption} Controls
</h5>
<form autoComplete="off" style={{margin: '0 -6px'}}>
<form autoComplete="off" className="form-group-wrapper">
<div className="form-group col-sm-12">
<label htmlFor="prefix">Title</label>
<OptIn
@ -139,7 +143,7 @@ class AxesOptions extends Component {
<Tabber
labelText="Y-Value's Format"
tipID="Y-Values's Format"
tipContent={TOOLTIP_CONTENT.FORMAT}
tipContent={TOOLTIP_Y_VALUE_FORMAT}
>
<Tab
text="K/M/B"

View File

@ -1,9 +1,10 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ResizeContainer from 'shared/components/ResizeContainer'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import QueryMaker from 'src/dashboards/components/QueryMaker'
import Visualization from 'src/dashboards/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls'
@ -20,10 +21,10 @@ import {
removeUnselectedTemplateValues,
TYPE_QUERY_CONFIG,
} from 'src/dashboards/constants'
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'shared/constants'
import {stringifyColorValues} from 'src/dashboards/constants/gaugeColors'
import {AUTO_GROUP_BY} from 'src/shared/constants'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
class CellEditorOverlay extends Component {
constructor(props) {
@ -105,7 +106,7 @@ class CellEditorOverlay extends Component {
handleSaveCell = () => {
const {queriesWorkingDraft, staticLegend} = this.state
const {cell, singleStatColors, gaugeColors} = this.props
const {cell, thresholdsListColors, gaugeColors} = this.props
const queries = queriesWorkingDraft.map(q => {
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
@ -119,13 +120,18 @@ class CellEditorOverlay extends Component {
})
let colors = []
if (cell.type === 'gauge') {
colors = stringifyColorValues(gaugeColors)
} else if (
cell.type === 'single-stat' ||
cell.type === 'line-plus-single-stat'
) {
colors = stringifyColorValues(singleStatColors)
switch (cell.type) {
case 'gauge': {
colors = stringifyColorValues(gaugeColors)
break
}
case 'single-stat':
case 'line-plus-single-stat':
case 'table': {
colors = stringifyColorValues(thresholdsListColors)
break
}
}
this.props.onSave({
@ -374,8 +380,8 @@ CellEditorOverlay.propTypes = {
}).isRequired,
dashboardID: string.isRequired,
sources: arrayOf(shape()),
singleStatType: string.isRequired,
singleStatColors: arrayOf(shape({}).isRequired).isRequired,
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import TemplateControlBar from 'src/dashboards/components/TemplateControlBar'
@ -15,7 +16,6 @@ const Dashboard = ({
autoRefresh,
manualRefresh,
onDeleteCell,
synchronizer,
onPositionChange,
inPresentationMode,
onOpenTemplateManager,
@ -25,6 +25,8 @@ const Dashboard = ({
showTemplateControlBar,
setScrollTop,
inView,
onSetHoverTime,
hoverTime,
}) => {
const cells = dashboard.cells.map(cell => {
const dashboardCell = {
@ -65,7 +67,8 @@ const Dashboard = ({
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
synchronizer={synchronizer}
hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange}
templates={templatesIncludingDashTime}
@ -111,7 +114,8 @@ Dashboard.propTypes = {
onPositionChange: func,
onDeleteCell: func,
onSummonOverlayTechnologies: func,
synchronizer: func,
hoverTime: string,
onSetHoverTime: func,
source: shape({
links: shape({
proxy: string,

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {
DASHBOARD_NAME_MAX_LENGTH,
NEW_DASHBOARD,

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import _ from 'lodash'
import classnames from 'classnames'

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
@ -45,12 +46,12 @@ class DashboardsPageContents extends Component {
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">
{tableHeader}
</h2>
<div className="u-flex u-ai-center dashboards-page--actions">
<div className="dashboards-page--actions">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import _ from 'lodash'

Some files were not shown because too many files have changed in this diff Show More