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] [bumpversion]
current_version = 1.4.1.3 current_version = 1.4.2.3
files = README.md server/swagger.json files = README.md server/swagger.json
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+)
serialize = {major}.{minor}.{patch}.{release} serialize = {major}.{minor}.{patch}.{release}

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ chronograf*.db
*_gen.go *_gen.go
canned/apps_gen.go canned/apps_gen.go
npm-debug.log 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 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 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 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/). [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 Installing Go
------------- -------------
@ -62,13 +66,13 @@ running the following:
gvm use go1.7.5 --default 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 * [Install Dep](https://github.com/golang/dep)
go get github.com/sparrc/gdm
``` 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 Revision Control Systems
------------------------ ------------------------

89
Gopkg.lock generated
View File

@ -39,34 +39,7 @@
[[projects]] [[projects]]
name = "github.com/gogo/protobuf" name = "github.com/gogo/protobuf"
packages = [ 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"]
"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" revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
[[projects]] [[projects]]
@ -77,13 +50,7 @@
[[projects]] [[projects]]
name = "github.com/google/go-cmp" name = "github.com/google/go-cmp"
packages = [ packages = ["cmp","cmp/cmpopts","cmp/internal/diff","cmp/internal/function","cmp/internal/value"]
"cmp",
"cmp/cmpopts",
"cmp/internal/diff",
"cmp/internal/function",
"cmp/internal/value"
]
revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97" revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97"
version = "v0.1.0" version = "v0.1.0"
@ -100,28 +67,13 @@
[[projects]] [[projects]]
name = "github.com/influxdata/influxdb" name = "github.com/influxdata/influxdb"
packages = [ packages = ["influxql","influxql/internal","influxql/neldermead","models","pkg/escape"]
"influxql",
"influxql/internal",
"influxql/neldermead",
"models",
"pkg/escape"
]
revision = "cd9363b52cac452113b95554d98a6be51beda24e" revision = "cd9363b52cac452113b95554d98a6be51beda24e"
version = "v1.1.5" version = "v1.1.5"
[[projects]] [[projects]]
name = "github.com/influxdata/kapacitor" name = "github.com/influxdata/kapacitor"
packages = [ packages = ["client/v1","pipeline","pipeline/tick","services/k8s/client","tick","tick/ast","tick/stateful","udf/agent"]
"client/v1",
"pipeline",
"pipeline/tick",
"services/k8s/client",
"tick",
"tick/ast",
"tick/stateful",
"udf/agent"
]
revision = "6de30070b39afde111fea5e041281126fe8aae31" revision = "6de30070b39afde111fea5e041281126fe8aae31"
[[projects]] [[projects]]
@ -163,21 +115,13 @@
[[projects]] [[projects]]
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = ["context","context/ctxhttp"]
"context",
"context/ctxhttp"
]
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe" revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
[[projects]] [[projects]]
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
packages = [ packages = [".","github","heroku","internal"]
".", revision = "2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0"
"github",
"heroku",
"internal"
]
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -187,31 +131,18 @@
[[projects]] [[projects]]
name = "google.golang.org/api" name = "google.golang.org/api"
packages = [ packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
"gensupport",
"googleapi",
"googleapi/internal/uritemplates",
"oauth2/v2"
]
revision = "bc20c61134e1d25265dd60049f5735381e79b631" revision = "bc20c61134e1d25265dd60049f5735381e79b631"
[[projects]] [[projects]]
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
packages = [ packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0" version = "v1.0.0"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "11df631364d11bc05c8f71af1aa735360b5a40a793d32d47d1f1d8c694a55f6f" inputs-digest = "a4df1b0953349e64a89581f4b83ac3a2f40e17681e19f8de3cbf828b6375a3ba"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

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

View File

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

View File

@ -136,7 +136,7 @@ option.
## Versions ## Versions
The most recent version of Chronograf is 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 Spotted a bug or have a feature request? Please open
[an issue](https://github.com/influxdata/chronograf/issues/new)! [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: To get started right away with Docker, you can pull down our latest release:
```sh ```sh
docker pull chronograf:1.4.1.3 docker pull chronograf:1.4.2.3
``` ```
### From Source ### From Source

View File

@ -75,14 +75,15 @@ func UnmarshalSource(data []byte, s *chronograf.Source) error {
// MarshalServer encodes a server to binary protobuf format. // MarshalServer encodes a server to binary protobuf format.
func MarshalServer(s chronograf.Server) ([]byte, error) { func MarshalServer(s chronograf.Server) ([]byte, error) {
return proto.Marshal(&Server{ return proto.Marshal(&Server{
ID: int64(s.ID), ID: int64(s.ID),
SrcID: int64(s.SrcID), SrcID: int64(s.SrcID),
Name: s.Name, Name: s.Name,
Username: s.Username, Username: s.Username,
Password: s.Password, Password: s.Password,
URL: s.URL, URL: s.URL,
Active: s.Active, Active: s.Active,
Organization: s.Organization, Organization: s.Organization,
InsecureSkipVerify: s.InsecureSkipVerify,
}) })
} }
@ -101,6 +102,7 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error {
s.URL = pb.URL s.URL = pb.URL
s.Active = pb.Active s.Active = pb.Active
s.Organization = pb.Organization s.Organization = pb.Organization
s.InsecureSkipVerify = pb.InsecureSkipVerify
return nil 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{ cells[i] = &DashboardCell{
ID: c.ID, ID: c.ID,
X: c.X, X: c.X,
@ -278,6 +301,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Type: c.Legend.Type, Type: c.Legend.Type,
Orientation: c.Legend.Orientation, Orientation: c.Legend.Orientation,
}, },
TableOptions: tableOptions,
} }
} }
templates := make([]*Template, len(d.Templates)) templates := make([]*Template, len(d.Templates))
@ -404,18 +428,48 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
legend.Orientation = c.Legend.Orientation 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{ cells[i] = chronograf.DashboardCell{
ID: c.ID, ID: c.ID,
X: c.X, X: c.X,
Y: c.Y, Y: c.Y,
W: c.W, W: c.W,
H: c.H, H: c.H,
Name: c.Name, Name: c.Name,
Queries: queries, Queries: queries,
Type: c.Type, Type: cellType,
Axes: axes, Axes: axes,
CellColors: colors, CellColors: colors,
Legend: legend, Legend: legend,
TableOptions: tableOptions,
} }
} }

View File

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

View File

@ -76,12 +76,13 @@ func TestMarshalSourceWithSecret(t *testing.T) {
func TestMarshalServer(t *testing.T) { func TestMarshalServer(t *testing.T) {
v := chronograf.Server{ v := chronograf.Server{
ID: 12, ID: 12,
SrcID: 2, SrcID: 2,
Name: "Fountain of Truth", Name: "Fountain of Truth",
Username: "docbrown", Username: "docbrown",
Password: "1 point twenty-one g1g@w@tts", Password: "1 point twenty-one g1g@w@tts",
URL: "http://oldmanpeabody.mall.io:9092", URL: "http://oldmanpeabody.mall.io:9092",
InsecureSkipVerify: true,
} }
var vv chronograf.Server var vv chronograf.Server
@ -193,6 +194,10 @@ func Test_MarshalDashboard(t *testing.T) {
Value: "100", Value: "100",
}, },
}, },
TableOptions: chronograf.TableOptions{
TimeFormat: "",
ColumnNames: []chronograf.TableColumn{},
},
}, },
}, },
Templates: []chronograf.Template{}, Templates: []chronograf.Template{},
@ -255,6 +260,9 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static", Type: "static",
Orientation: "bottom", Orientation: "bottom",
}, },
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
Type: "line", Type: "line",
}, },
}, },
@ -309,6 +317,10 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static", Type: "static",
Orientation: "bottom", Orientation: "bottom",
}, },
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
},
Type: "line", Type: "line",
}, },
}, },
@ -369,6 +381,9 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
}, },
}, },
Type: "line", Type: "line",
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
}, },
}, },
Templates: []chronograf.Template{}, Templates: []chronograf.Template{},
@ -418,6 +433,10 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
Value: "100", Value: "100",
}, },
}, },
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
},
Type: "line", 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)) 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{ srcs := []chronograf.Server{
chronograf.Server{ chronograf.Server{
Name: "Of Truth", Name: "Of Truth",
SrcID: 10, SrcID: 10,
Username: "marty", Username: "marty",
Password: "I❤ jennifer parker", Password: "I❤ jennifer parker",
URL: "toyota-hilux.lyon-estates.local", URL: "toyota-hilux.lyon-estates.local",
Active: false, Active: false,
Organization: "133", Organization: "133",
InsecureSkipVerify: true,
}, },
chronograf.Server{ chronograf.Server{
Name: "HipToBeSquare", Name: "HipToBeSquare",
SrcID: 12, SrcID: 12,
Username: "calvinklein", Username: "calvinklein",
Password: "chuck b3rry", Password: "chuck b3rry",
URL: "toyota-hilux.lyon-estates.local", URL: "toyota-hilux.lyon-estates.local",
Active: false, Active: false,
Organization: "133", 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 // Delete a user from the UsersStore
func (s *UsersStore) Delete(ctx context.Context, usr *chronograf.User) error { func (s *UsersStore) Delete(ctx context.Context, u *chronograf.User) error {
_, err := s.get(ctx, usr.ID) _, err := s.get(ctx, u.ID)
if err != nil { if err != nil {
return err return err
} }
return s.client.db.Update(func(tx *bolt.Tx) error { 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 // Update a user
func (s *UsersStore) Update(ctx context.Context, usr *chronograf.User) error { func (s *UsersStore) Update(ctx context.Context, u *chronograf.User) error {
_, err := s.get(ctx, usr.ID) _, err := s.get(ctx, u.ID)
if err != nil { if err != nil {
return err return err
} }
return s.client.db.Update(func(tx *bolt.Tx) error { 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 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 err
} }
return nil return nil

View File

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

View File

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

View File

@ -35,6 +35,9 @@ const (
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization") ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
ErrConfigNotFound = Error("cannot find configuration") ErrConfigNotFound = Error("cannot find configuration")
ErrAnnotationNotFound = Error("annotation not found") 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 // 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 // DashboardCell holds visual and query information for a cell
type DashboardCell struct { type DashboardCell struct {
ID string `json:"i"` ID string `json:"i"`
X int32 `json:"x"` X int32 `json:"x"`
Y int32 `json:"y"` Y int32 `json:"y"`
W int32 `json:"w"` W int32 `json:"w"`
H int32 `json:"h"` H int32 `json:"h"`
Name string `json:"name"` Name string `json:"name"`
Queries []DashboardQuery `json:"queries"` Queries []DashboardQuery `json:"queries"`
Axes map[string]Axis `json:"axes"` Axes map[string]Axis `json:"axes"`
Type string `json:"type"` Type string `json:"type"`
CellColors []CellColor `json:"colors"` CellColors []CellColor `json:"colors"`
Legend Legend `json:"legend"` 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 // 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. // Cell is a rectangle and multiple time series queries to visualize.
type Cell struct { type Cell struct {
X int32 `json:"x"` X int32 `json:"x"`
Y int32 `json:"y"` Y int32 `json:"y"`
W int32 `json:"w"` W int32 `json:"w"`
H int32 `json:"h"` H int32 `json:"h"`
I string `json:"i"` I string `json:"i"`
Name string `json:"name"` Name string `json:"name"`
Queries []Query `json:"queries"` Queries []Query `json:"queries"`
Axes map[string]Axis `json:"axes"` Axes map[string]Axis `json:"axes"`
Type string `json:"type"` Type string `json:"type"`
CellColors []CellColor `json:"colors"`
} }
// Layout is a collection of Cells for visualization // 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. // SortTemplates the templates by size, then type, then value.
func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar { func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar {
sort.Slice(ts, func(i, j int) bool { sort.Slice(ts, func(i, j int) bool {
if ts[i].Var == ":interval:" {
return false
}
if len(ts[i].Values) != len(ts[j].Values) { if len(ts[i].Values) != len(ts[j].Values) {
return 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 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 { if res, ok := tv["resolution"]; ok {
resolution, err := strconv.ParseInt(res, 0, 64) resolution, err := strconv.ParseInt(res, 0, 64)
if err != nil { if err != nil {
@ -101,22 +83,6 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
return query, nil 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 // AutoGroupBy generates the time to group by in order to decimate the number of
// points returned in a query // points returned in a query
func AutoGroupBy(resolution, pixelsPerPoint int64, duration time.Duration) string { 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"`, 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", name: "auto group by",
query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by :interval:`, 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:", Var: ":interval:",
Values: []chronograf.TemplateValue{ Values: []chronograf.TemplateValue{
{ {
Value: "999", Value: "1000",
Type: "resolution", 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", name: "auto group by without duration",
@ -185,7 +153,7 @@ func TestTemplateReplace(t *testing.T) {
Var: ":interval:", Var: ":interval:",
Values: []chronograf.TemplateValue{ Values: []chronograf.TemplateValue{
{ {
Value: "999", Value: "1000",
Type: "resolution", 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:", name: "auto group by with :dashboardTime:",

View File

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

View File

@ -2,6 +2,7 @@ package oauth2
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
@ -61,7 +62,19 @@ func (h *Heroku) PrincipalID(provider *http.Client) (string, error) {
DefaultOrganization DefaultOrg `json:"default_organization"` 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 { if err != nil {
h.Logger.Error("Unable to communicate with Heroku. err:", err) h.Logger.Error("Unable to communicate with Heroku. err:", err)
return "", err return "", err

View File

@ -1,6 +1,7 @@
package oauth2 package oauth2
import ( import (
"encoding/json"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/http/httptest" "net/http/httptest"
@ -13,15 +14,24 @@ import (
var testTime = time.Date(1985, time.October, 25, 18, 0, 0, 0, time.UTC) 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 // setupMuxTest produces an http.Client and an httptest.Server configured to
// use a particular http.Handler selected from a AuthMux. As this selection is // use a particular http.Handler selected from a AuthMux. As this selection is
// done during the setup process, this configuration is performed by providing // done during the setup process, this configuration is performed by providing
// a function, and returning the desired handler. Cleanup is still the // a function, and returning the desired handler. Cleanup is still the
// responsibility of the test writer, so the httptest.Server's Close() method // responsibility of the test writer, so the httptest.Server's Close() method
// should be deferred. // 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) { provider := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("content-type", "application/json")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
body, _ := json.Marshal(response)
rw.Write(body)
})) }))
now := func() time.Time { 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) { func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) {
t.Parallel() 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() return j.Logout()
}) })
defer teardownMuxTest(hc, ts, prov) 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) { func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) {
t.Parallel() 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. return j.Login() // Use Login handler for httptest server.
}) })
defer teardownMuxTest(hc, ts, prov) 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) { 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() return j.Callback()
}) })
defer teardownMuxTest(hc, ts, prov) 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 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 Username string `json:"username,omitempty"` // Username for authentication to kapacitor
Password string `json:"password,omitempty"` 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"` Active bool `json:"active"`
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to 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) URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092)
Username string `json:"username,omitempty"` // Username for authentication to kapacitor Username string `json:"username,omitempty"` // Username for authentication to kapacitor
Password string `json:"password,omitempty"` 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"` Active bool `json:"active"`
Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor 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 URL *string `json:"url,omitempty"` // URL for the kapacitor
Username *string `json:"username,omitempty"` // Username for kapacitor auth Username *string `json:"username,omitempty"` // Username for kapacitor auth
Password *string `json:"password,omitempty"` 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"` 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)) 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 { for _, axis := range axes {
if _, found := cell.Axes[axis]; !found { if _, found := cell.Axes[axis]; !found {
layout.Cells[idx].Axes[axis] = chronograf.Axis{ layout.Cells[idx].Axes[axis] = chronograf.Axis{

View File

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

View File

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

View File

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

View File

@ -1,20 +1,26 @@
{ {
parser: 'babel-eslint', "parser": "babel-eslint",
plugins: [ plugins: [
'react', 'react',
'prettier', 'prettier',
'babel', 'babel',
'jest',
],
extends: [
"prettier",
"prettier/react"
], ],
env: { env: {
browser: true, browser: true,
mocha: true, mocha: true,
"jest": true,
node: true, node: true,
es6: true, es6: true,
}, },
globals: { globals: {
expect: true, expect: true,
}, },
"parserOptions": { parserOptions: {
ecmaFeatures: { ecmaFeatures: {
arrowFunctions: true, arrowFunctions: true,
binaryLiterals: true, binaryLiterals: true,
@ -244,6 +250,10 @@
'semi': false, 'semi': false,
}], }],
// jest
'jest/no-disabled-tests': "warn",
'jest/no-focused-tests': "error",
// Babel // Babel
'babel/no-invalid-this': 1 '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') const webpack = require('webpack')
var path = require('path') const path = require('path')
module.exports = function(config) { module.exports = function(config) {
config.set({ config.set({
browsers: ['PhantomJS'], browsers: ['PhantomJS'],
singleRun: true, frameworks: ['mocha'],
frameworks: ['mocha', 'sinon-chai'],
files: [ files: [
'node_modules/babel-polyfill/dist/polyfill.js', 'node_modules/babel-polyfill/dist/polyfill.js',
'spec/spec-helper.js', 'spec/spec-helper.js',
@ -42,10 +41,6 @@ module.exports = function(config) {
test: /sinon\/pkg\/sinon\.js/, test: /sinon\/pkg\/sinon\.js/,
loader: 'imports?define=>false,require=>false', loader: 'imports?define=>false,require=>false',
}, },
{
test: /\.json$/,
loader: 'json',
},
], ],
}, },
externals: { externals: {
@ -61,7 +56,6 @@ module.exports = function(config) {
shared: path.resolve(__dirname, 'src', 'shared'), shared: path.resolve(__dirname, 'src', 'shared'),
style: path.resolve(__dirname, 'src', 'style'), style: path.resolve(__dirname, 'src', 'style'),
utils: path.resolve(__dirname, 'src', 'utils'), 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", "name": "chronograf-ui",
"version": "1.4.1-3", "version": "1.4.2-3",
"private": false, "private": false,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"description": "", "description": "",
@ -9,17 +9,19 @@
"url": "github:influxdata/chronograf" "url": "github:influxdata/chronograf"
}, },
"scripts": { "scripts": {
"build": "yarn run clean && env NODE_ENV=production webpack --optimize-minimize --config ./webpack/prodConfig.js", "build": "yarn run clean && webpack --config ./webpack/prod.config.js",
"build:dev": "webpack --config ./webpack/devConfig.js", "build:dev": "webpack --config ./webpack/dev.config.js",
"start": "yarn run clean && webpack --watch --config ./webpack/devConfig.js", "build:vendor": "webpack --config webpack/vendor.config.js",
"start:hmr": "webpack-dev-server --open --config ./webpack/devConfig.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/", "lint": "esw src/",
"test": "karma start", "test": "jest",
"test:integration": "nightwatch tests --skip",
"test:lint": "yarn run lint; yarn run test", "test:lint": "yarn run lint; yarn run test",
"test:dev": "concurrently \"yarn run lint --watch\" \"yarn run test --no-single-run --reporters=verbose\"", "test:watch": "jest --watch",
"clean": "rm -rf build/*", "clean": "rm -rf ./build/*",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix" "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": "", "author": "",
"eslintConfig": { "eslintConfig": {
@ -28,10 +30,18 @@
} }
}, },
"devDependencies": { "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", "autoprefixer": "^6.3.1",
"babel-core": "^6.5.1", "babel-core": "^6.5.1",
"babel-eslint": "6.1.2", "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-lodash": "^2.0.1",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0", "babel-plugin-syntax-trailing-function-commas": "^6.5.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4", "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-react-remove-prop-types": "^0.2.1",
"babel-plugin-transform-runtime": "^6.5.0", "babel-plugin-transform-runtime": "^6.5.0",
"babel-polyfill": "^6.13.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-react": "^6.5.0",
"babel-preset-stage-0": "^6.16.0", "babel-preset-stage-0": "^6.16.0",
"babel-runtime": "^6.5.0", "babel-runtime": "^6.5.0",
"bower": "^1.7.7", "compression-webpack-plugin": "^1.1.8",
"chai": "^3.5.0",
"concurrently": "^3.5.0", "concurrently": "^3.5.0",
"core-js": "^2.1.3", "core-js": "^2.1.3",
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"envify": "^3.4.0", "envify": "^3.4.0",
"enzyme": "^2.4.1", "enzyme": "^3.3.0",
"enzyme-adapter-react-15": "^1.0.5",
"eslint": "^3.14.1", "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-prettier": "^2.1.2",
"eslint-plugin-react": "6.6.0", "eslint-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2", "eslint-watch": "^3.1.2",
"express": "^4.14.0", "express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1", "extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^0.8.5", "file-loader": "^1.1.7",
"fork-ts-checker-webpack-plugin": "^0.3.0",
"hanson": "^1.1.1", "hanson": "^1.1.1",
"hson-loader": "^1.0.0", "html-webpack-include-assets-plugin": "^1.0.2",
"html-webpack-plugin": "^2.22.0", "html-webpack-plugin": "^2.30.1",
"imports-loader": "^0.6.5", "imports-loader": "^0.6.5",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"jsdom": "^9.0.0", "jsdom": "^9.0.0",
"json-loader": "^0.5.4", "json-loader": "^0.5.7",
"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",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
"on-build-webpack": "^0.1.0", "on-build-webpack": "^0.1.0",
"postcss-browser-reporter": "^0.4.0", "postcss-browser-reporter": "^0.4.0",
@ -82,23 +86,26 @@
"postcss-loader": "^0.8.0", "postcss-loader": "^0.8.0",
"postcss-reporter": "^1.3.1", "postcss-reporter": "^1.3.1",
"precss": "^1.4.0", "precss": "^1.4.0",
"prettier": "^1.5.3", "prettier": "1.5.3",
"react-addons-test-utils": "^15.0.2", "react-addons-test-utils": "^15.0.2",
"resolve-url-loader": "^1.6.0", "react-test-renderer": "^15.6.1",
"sass-loader": "^3.2.0", "resolve-url-loader": "^2.2.1",
"sinon": "^1.17.4", "sass-loader": "^6.0.6",
"sinon-chai": "^2.8.0",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"testem": "^1.2.1", "thread-loader": "^1.1.5",
"uglify-js": "^2.6.1", "ts-jest": "^22.4.1",
"webpack": "^1.13.0", "ts-loader": "^3.5.0",
"webpack-dev-server": "^1.14.1" "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": { "dependencies": {
"@skidding/react-codemirror": "^1.0.1", "@skidding/react-codemirror": "^1.0.1",
"axios": "^0.13.1", "axios": "^0.13.1",
"bignumber.js": "^4.0.2", "bignumber.js": "^4.0.2",
"bootstrap": "^3.3.7",
"calculate-size": "^1.1.1", "calculate-size": "^1.1.1",
"classnames": "^2.2.3", "classnames": "^2.2.3",
"dygraphs": "2.1.0", "dygraphs": "2.1.0",
@ -106,30 +113,29 @@
"fast.js": "^0.1.1", "fast.js": "^0.1.1",
"fixed-data-table": "^0.6.1", "fixed-data-table": "^0.6.1",
"he": "^1.1.1", "he": "^1.1.1",
"jquery": "^3.1.0",
"lodash": "^4.3.0", "lodash": "^4.3.0",
"moment": "^2.13.0", "moment": "^2.13.0",
"nano-date": "^2.0.1", "nano-date": "^2.0.1",
"node-uuid": "^1.4.7", "prop-types": "^15.6.1",
"query-string": "^5.0.0", "query-string": "^5.0.0",
"react": "^15.0.2", "react": "^15.0.2",
"react-addons-shallow-compare": "^15.0.2", "react-addons-shallow-compare": "^15.0.2",
"react-codemirror": "^1.0.0", "react-codemirror": "^1.0.0",
"react-component-resizable": "^1.1.0-rc1",
"react-custom-scrollbars": "^4.1.1", "react-custom-scrollbars": "^4.1.1",
"react-dimensions": "^1.2.0", "react-dimensions": "^1.2.0",
"react-dom": "^15.0.2", "react-dom": "^15.0.2",
"react-grid-layout": "^0.13.9", "react-grid-layout": "^0.16.6",
"react-onclickoutside": "^5.2.0", "react-onclickoutside": "^5.2.0",
"react-redux": "^4.4.0", "react-redux": "^4.4.0",
"react-resizable": "^1.7.5",
"react-router": "^3.0.2", "react-router": "^3.0.2",
"react-router-redux": "^4.0.8", "react-router-redux": "^4.0.8",
"react-sparklines": "^1.4.2",
"react-tooltip": "^3.2.1", "react-tooltip": "^3.2.1",
"react-virtualized": "^9.18.5",
"redux": "^3.3.1", "redux": "^3.3.1",
"redux-auth-wrapper": "^1.0.0", "redux-auth-wrapper": "^1.0.0",
"redux-thunk": "^1.0.3", "redux-thunk": "^1.0.3",
"rome": "^2.1.22", "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 React from 'react'
import {connect} from 'react-redux' import PropTypes from 'prop-types'
import {bindActionCreators} from 'redux'
import SideNav from 'src/side_nav' import SideNav from 'src/side_nav'
import Notifications from 'shared/components/Notifications' 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({ App.propTypes = {
propTypes: { children: node.isRequired,
children: node.isRequired, }
notify: func.isRequired,
},
handleAddFlashMessage({type, text}) { export default App
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)

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 {withRouter} from 'react-router'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
@ -14,9 +15,17 @@ import {showDatabases} from 'shared/apis/metaQuery'
import {getSourcesAsync} from 'shared/actions/sources' import {getSourcesAsync} from 'shared/actions/sources'
import {errorThrown as errorThrownAction} from 'shared/actions/errors' 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 {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 // 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. // 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) { if (!isFetching && isUsingAuth && !organizations.length) {
notify( notify(NOTIFY_USER_REMOVED_FROM_ALL_ORGS)
'error',
'You have been removed from all organizations. Please contact your administrator.'
)
return router.push('/purgatory') return router.push('/purgatory')
} }
@ -95,7 +101,7 @@ class CheckSources extends Component {
me.superAdmin && me.superAdmin &&
!organizations.find(o => o.id === currentOrganization.id) !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') return router.push('/purgatory')
} }
@ -117,7 +123,7 @@ class CheckSources extends Component {
return router.push(`/sources/${sources[0].id}/${restString}`) return router.push(`/sources/${sources[0].id}/${restString}`)
} }
// if you're a viewer and there are no sources, go to purgatory. // 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') return router.push('/purgatory')
} }
@ -142,18 +148,12 @@ class CheckSources extends Component {
try { try {
const newSources = await getSources() const newSources = await getSources()
if (newSources.length) { if (newSources.length) {
errorThrown( errorThrown(error, NOTIFY_SOURCE_NO_LONGER_AVAILABLE(source.name))
error,
`Source ${source.name} is no longer available. Successfully connected to another source.`
)
} else { } else {
errorThrown( errorThrown(error, NOTIFY_NO_SOURCES_AVAILABLE(source.name))
error,
`Unable to connect to source ${source.name}. No other sources available.`
)
} }
} catch (error2) { } 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 => ({ const mapDispatchToProps = dispatch => ({
getSources: bindActionCreators(getSourcesAsync, dispatch), getSources: bindActionCreators(getSourcesAsync, dispatch),
errorThrown: bindActionCreators(errorThrownAction, dispatch), errorThrown: bindActionCreators(errorThrownAction, dispatch),
notify: bindActionCreators(publishNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)( export default connect(mapStateToProps, mapDispatchToProps)(

View File

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

View File

@ -18,9 +18,40 @@ import {
import {killQuery as killQueryProxy} from 'shared/apis/metaQuery' 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 {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 {REVERT_STATE_DELAY} from 'shared/constants'
import _ from 'lodash' import _ from 'lodash'
@ -276,12 +307,12 @@ export const loadDBsAndRPsAsync = url => async dispatch => {
export const createUserAsync = (url, user) => async dispatch => { export const createUserAsync = (url, user) => async dispatch => {
try { try {
const {data} = await createUserAJAX(url, user) const {data} = await createUserAJAX(url, user)
dispatch( dispatch(notify(NOTIFY_DB_USER_CREATED))
publishAutoDismissingNotification('success', 'User created successfully')
)
dispatch(syncUser(user, data)) dispatch(syncUser(user, data))
} catch (error) { } 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 // undo optimistic update
setTimeout(() => dispatch(deleteUser(user)), REVERT_STATE_DELAY) 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 => { export const createRoleAsync = (url, role) => async dispatch => {
try { try {
const {data} = await createRoleAJAX(url, role) const {data} = await createRoleAJAX(url, role)
dispatch( dispatch(notify(NOTIFY_ROLE_CREATED))
publishAutoDismissingNotification('success', 'Role created successfully')
)
dispatch(syncRole(role, data)) dispatch(syncRole(role, data))
} catch (error) { } 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 // undo optimistic update
setTimeout(() => dispatch(deleteRole(role)), REVERT_STATE_DELAY) setTimeout(() => dispatch(deleteRole(role)), REVERT_STATE_DELAY)
} }
@ -305,15 +336,10 @@ export const createDatabaseAsync = (url, database) => async dispatch => {
try { try {
const {data} = await createDatabaseAJAX(url, database) const {data} = await createDatabaseAJAX(url, database)
dispatch(syncDatabase(database, data)) dispatch(syncDatabase(database, data))
dispatch( dispatch(notify(NOTIFY_DATABASE_CREATED))
publishAutoDismissingNotification(
'success',
'Database created successfully'
)
)
} catch (error) { } catch (error) {
dispatch( dispatch(
errorThrown(error, `Failed to create database: ${error.data.message}`) errorThrown(error, NOTIFY_DATABASE_CREATION_FAILED(error.data.message))
) )
// undo optimistic update // undo optimistic update
setTimeout(() => dispatch(removeDatabase(database)), REVERT_STATE_DELAY) setTimeout(() => dispatch(removeDatabase(database)), REVERT_STATE_DELAY)
@ -329,19 +355,11 @@ export const createRetentionPolicyAsync = (
database.links.retentionPolicies, database.links.retentionPolicies,
retentionPolicy retentionPolicy
) )
dispatch( dispatch(notify(NOTIFY_RETENTION_POLICY_CREATED))
publishAutoDismissingNotification(
'success',
'Retention policy created successfully'
)
)
dispatch(syncRetentionPolicy(database, retentionPolicy, data)) dispatch(syncRetentionPolicy(database, retentionPolicy, data))
} catch (error) { } catch (error) {
dispatch( dispatch(
errorThrown( errorThrown(NOTIFY_RETENTION_POLICY_CREATION_FAILED(error.data.message))
error,
`Failed to create retention policy: ${error.data.message}`
)
) )
// undo optimistic update // undo optimistic update
setTimeout( setTimeout(
@ -360,18 +378,13 @@ export const updateRetentionPolicyAsync = (
dispatch(editRetentionPolicyRequested(database, oldRP, newRP)) dispatch(editRetentionPolicyRequested(database, oldRP, newRP))
const {data} = await updateRetentionPolicyAJAX(oldRP.links.self, newRP) const {data} = await updateRetentionPolicyAJAX(oldRP.links.self, newRP)
dispatch(editRetentionPolicyCompleted(database, oldRP, data)) dispatch(editRetentionPolicyCompleted(database, oldRP, data))
dispatch( dispatch(notify(NOTIFY_RETENTION_POLICY_UPDATED))
publishAutoDismissingNotification(
'success',
'Retention policy updated successfully'
)
)
} catch (error) { } catch (error) {
dispatch(editRetentionPolicyFailed(database, oldRP)) dispatch(editRetentionPolicyFailed(database, oldRP))
dispatch( dispatch(
errorThrown( errorThrown(
error, 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)) dispatch(deleteRole(role))
try { try {
await deleteRoleAJAX(role.links.self) await deleteRoleAJAX(role.links.self)
dispatch(publishAutoDismissingNotification('success', 'Role deleted')) dispatch(notify(NOTIFY_ROLE_DELETED(role.name)))
} catch (error) { } 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)) dispatch(deleteUser(user))
try { try {
await deleteUserAJAX(user.links.self) await deleteUserAJAX(user.links.self)
dispatch(publishAutoDismissingNotification('success', 'User deleted')) dispatch(notify(NOTIFY_DB_USER_DELETED(user.name)))
} catch (error) { } 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)) dispatch(removeDatabase(database))
try { try {
await deleteDatabaseAJAX(database.links.self) await deleteDatabaseAJAX(database.links.self)
dispatch(publishAutoDismissingNotification('success', 'Database deleted')) dispatch(notify(NOTIFY_DATABASE_DELETED(database.name)))
} catch (error) { } catch (error) {
dispatch( 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)) dispatch(removeRetentionPolicy(database, retentionPolicy))
try { try {
await deleteRetentionPolicyAJAX(retentionPolicy.links.self) await deleteRetentionPolicyAJAX(retentionPolicy.links.self)
dispatch( dispatch(notify(NOTIFY_RETENTION_POLICY_DELETED(retentionPolicy.name)))
publishAutoDismissingNotification(
'success',
`Retention policy ${retentionPolicy.name} deleted`
)
)
} catch (error) { } catch (error) {
dispatch( dispatch(
errorThrown( errorThrown(
error, 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, users,
role.permissions role.permissions
) )
dispatch(publishAutoDismissingNotification('success', 'Role users updated')) dispatch(notify(NOTIFY_ROLE_USERS_UPDATED))
dispatch(syncRole(role, data)) dispatch(syncRole(role, data))
} catch (error) { } 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, role.users,
permissions permissions
) )
dispatch( dispatch(notify(NOTIFY_ROLE_PERMISSIONS_UPDATED))
publishAutoDismissingNotification('success', 'Role permissions updated')
)
dispatch(syncRole(role, data)) dispatch(syncRole(role, data))
} catch (error) { } catch (error) {
dispatch( 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 => { ) => async dispatch => {
try { try {
const {data} = await updateUserAJAX(user.links.self, {permissions}) const {data} = await updateUserAJAX(user.links.self, {permissions})
dispatch( dispatch(notify(NOTIFY_DB_USER_PERMISSIONS_UPDATED))
publishAutoDismissingNotification('success', 'User permissions updated')
)
dispatch(syncUser(user, data)) dispatch(syncUser(user, data))
} catch (error) { } catch (error) {
dispatch( 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 => { export const updateUserRolesAsync = (user, roles) => async dispatch => {
try { try {
const {data} = await updateUserAJAX(user.links.self, {roles}) 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)) dispatch(syncUser(user, data))
} catch (error) { } catch (error) {
dispatch( 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 => { export const updateUserPasswordAsync = (user, password) => async dispatch => {
try { try {
const {data} = await updateUserAJAX(user.links.self, {password}) const {data} = await updateUserAJAX(user.links.self, {password})
dispatch( dispatch(notify(NOTIFY_DB_USER_PASSWORD_UPDATED))
publishAutoDismissingNotification('success', 'User password updated')
)
dispatch(syncUser(user, data)) dispatch(syncUser(user, data))
} catch (error) { } catch (error) {
dispatch( 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 {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import UsersTable from 'src/admin/components/UsersTable' import UsersTable from 'src/admin/components/UsersTable'
import RolesTable from 'src/admin/components/RolesTable' 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 OnClickOutside from 'shared/components/OnClickOutside'
import ConfirmButtons from 'shared/components/ConfirmButtons' import ConfirmButtons from 'shared/components/ConfirmButtons'
@ -9,18 +10,13 @@ class ChangePassRow extends Component {
this.state = { this.state = {
showForm: false, 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}) this.setState({showForm: true})
} }
handleCancel() { handleCancel = () => {
this.setState({showForm: false}) this.setState({showForm: false})
} }
@ -28,12 +24,12 @@ class ChangePassRow extends Component {
this.setState({showForm: false}) this.setState({showForm: false})
} }
handleSubmit(user) { handleSubmit = user => {
this.props.onApply(user) this.props.onApply(user)
this.setState({showForm: false}) this.setState({showForm: false})
} }
handleKeyPress(user) { handleKeyPress = user => {
return e => { return e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.handleSubmit(user) this.handleSubmit(user)
@ -41,7 +37,7 @@ class ChangePassRow extends Component {
} }
} }
handleEdit(user) { handleEdit = user => {
return e => { return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value}) 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' import DatabaseTable from 'src/admin/components/DatabaseTable'
const DatabaseManager = ({ const DatabaseManager = ({
databases, databases,
notify,
isRFDisplayed, isRFDisplayed,
isAddDBDisabled, isAddDBDisabled,
addDatabase, addDatabase,
@ -25,8 +25,8 @@ const DatabaseManager = ({
onDeleteRetentionPolicy, onDeleteRetentionPolicy,
}) => { }) => {
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{databases.length === 1 {databases.length === 1
? '1 Database' ? '1 Database'
@ -45,7 +45,6 @@ const DatabaseManager = ({
<DatabaseTable <DatabaseTable
key={db.links.self} key={db.links.self}
database={db} database={db}
notify={notify}
isRFDisplayed={isRFDisplayed} isRFDisplayed={isRFDisplayed}
onEditDatabase={onEditDatabase} onEditDatabase={onEditDatabase}
onKeyDownDatabase={onKeyDownDatabase} onKeyDownDatabase={onKeyDownDatabase}
@ -73,7 +72,6 @@ const {arrayOf, bool, func, shape} = PropTypes
DatabaseManager.propTypes = { DatabaseManager.propTypes = {
databases: arrayOf(shape()), databases: arrayOf(shape()),
notify: func,
addDatabase: func, addDatabase: func,
isRFDisplayed: bool, isRFDisplayed: bool,
isAddDBDisabled: 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 onClickOutside from 'react-onclickoutside'
import {notify as notifyAction} from 'shared/actions/notifications'
import {formatRPDuration} from 'utils/formatting' import {formatRPDuration} from 'utils/formatting'
import YesNoButtons from 'shared/components/YesNoButtons' import YesNoButtons from 'shared/components/YesNoButtons'
import {DATABASE_TABLE} from 'src/admin/constants/tableSizing' import {DATABASE_TABLE} from 'src/admin/constants/tableSizing'
import {NOTIFY_RETENTION_POLICY_CANT_HAVE_EMPTY_FIELDS} from 'shared/copy/notifications'
class DatabaseRow extends Component { class DatabaseRow extends Component {
constructor(props) { constructor(props) {
@ -109,7 +116,7 @@ class DatabaseRow extends Component {
const replication = isRFDisplayed ? +this.replication.value.trim() : 1 const replication = isRFDisplayed ? +this.replication.value.trim() : 1
if (!duration || (isRFDisplayed && !replication)) { if (!duration || (isRFDisplayed && !replication)) {
notify('error', 'Fields cannot be empty') notify(NOTIFY_RETENTION_POLICY_CANT_HAVE_EMPTY_FIELDS)
return return
} }
@ -260,8 +267,12 @@ DatabaseRow.propTypes = {
onCreate: func, onCreate: func,
onUpdate: func, onUpdate: func,
onDelete: func, onDelete: func,
notify: func, notify: func.isRequired,
isRFDisplayed: bool, 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 _ from 'lodash'
import classnames from 'classnames' import classnames from 'classnames'
@ -11,7 +12,6 @@ const {func, shape, bool} = PropTypes
const DatabaseTable = ({ const DatabaseTable = ({
database, database,
notify,
isRFDisplayed, isRFDisplayed,
onEditDatabase, onEditDatabase,
onKeyDownDatabase, onKeyDownDatabase,
@ -35,7 +35,6 @@ const DatabaseTable = ({
> >
<DatabaseTableHeader <DatabaseTableHeader
database={database} database={database}
notify={notify}
onEdit={onEditDatabase} onEdit={onEditDatabase}
onCancel={onCancelDatabase} onCancel={onCancelDatabase}
onDelete={onDeleteDatabase} onDelete={onDeleteDatabase}
@ -73,7 +72,6 @@ const DatabaseTable = ({
return ( return (
<DatabaseRow <DatabaseRow
key={rp.links.self} key={rp.links.self}
notify={notify}
database={database} database={database}
retentionPolicy={rp} retentionPolicy={rp}
onCreate={onCreateRetentionPolicy} onCreate={onCreateRetentionPolicy}
@ -95,7 +93,6 @@ const DatabaseTable = ({
DatabaseTable.propTypes = { DatabaseTable.propTypes = {
onEditDatabase: func, onEditDatabase: func,
database: shape(), database: shape(),
notify: func,
isRFDisplayed: bool, isRFDisplayed: bool,
isAddRPDisabled: bool, isAddRPDisabled: bool,
onKeyDownDatabase: func, 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 ConfirmButtons from 'shared/components/ConfirmButtons'
import {NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED} from 'shared/copy/notifications'
const DatabaseTableHeader = ({ const DatabaseTableHeader = ({
database, database,
@ -53,7 +60,7 @@ const Header = ({
onDatabaseDeleteConfirm, onDatabaseDeleteConfirm,
}) => { }) => {
const buttons = ( const buttons = (
<div className="text-right db-manager-header--actions"> <div className="db-manager-header--actions text-right">
<button <button
className="btn btn-xs btn-primary" className="btn btn-xs btn-primary"
disabled={isAddRPDisabled} disabled={isAddRPDisabled}
@ -74,7 +81,7 @@ const Header = ({
const onConfirm = db => { const onConfirm = db => {
if (database.deleteCode !== `DELETE ${database.name}`) { 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) onDelete(db)
@ -134,7 +141,7 @@ const {func, shape, bool} = PropTypes
DatabaseTableHeader.propTypes = { DatabaseTableHeader.propTypes = {
onEdit: func, onEdit: func,
notify: func, notify: func.isRequired,
database: shape(), database: shape(),
onKeyDown: func, onKeyDown: func,
onCancel: func, onCancel: func,
@ -148,7 +155,7 @@ DatabaseTableHeader.propTypes = {
} }
Header.propTypes = { Header.propTypes = {
notify: func, notify: func.isRequired,
onConfirm: func, onConfirm: func,
onCancel: func, onCancel: func,
onDelete: func, onDelete: func,
@ -168,4 +175,8 @@ EditHeader.propTypes = {
isRFDisplayed: bool, 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}) => const EmptyRow = ({tableName}) =>
<tr className="table-empty-state"> <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 { class FilterBar extends Component {
constructor(props) { constructor(props) {
@ -26,8 +27,8 @@ class FilterBar extends Component {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
}) })
return ( return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<div className="users__search-widget input-group admin__search-widget"> <div className="search-widget" style={{width: '300px'}}>
<input <input
type="text" type="text"
className="form-control input-sm" className="form-control input-sm"
@ -35,9 +36,7 @@ class FilterBar extends Component {
value={this.state.filterText} value={this.state.filterText}
onChange={this.handleText} onChange={this.handleText}
/> />
<div className="input-group-addon"> <span className="icon search" />
<span className="icon search" aria-hidden="true" />
</div>
</div> </div>
<button <button
className="btn btn-sm btn-primary" 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 QueryRow from 'src/admin/components/QueryRow'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing' import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
const QueriesTable = ({queries, onKillQuery}) => const QueriesTable = ({queries, onKillQuery}) =>
<div> <div>
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-body"> <div className="panel-body">
<table className="table v-center admin-table table-highlight"> <table className="table v-center admin-table table-highlight">
<thead> <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 ConfirmButtons from 'shared/components/ConfirmButtons'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing' import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
@ -7,24 +8,20 @@ class QueryRow extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.handleInitiateKill = ::this.handleInitiateKill
this.handleFinishHim = ::this.handleFinishHim
this.handleShowMercy = ::this.handleShowMercy
this.state = { this.state = {
confirmingKill: false, confirmingKill: false,
} }
} }
handleInitiateKill() { handleInitiateKill = () => {
this.setState({confirmingKill: true}) this.setState({confirmingKill: true})
} }
handleFinishHim() { handleFinishHim = () => {
this.props.onKill(this.props.query.id) this.props.onKill(this.props.query.id)
} }
handleShowMercy() { handleShowMercy = () => {
this.setState({confirmingKill: false}) 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' import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
class RoleEditingRow extends Component { class RoleEditingRow extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
} }
handleKeyPress(role) { handleKeyPress = role => {
return e => { return e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.props.onSave(role) this.props.onSave(role)
@ -18,7 +16,7 @@ class RoleEditingRow extends Component {
} }
} }
handleEdit(role) { handleEdit = role => {
return e => { return e => {
this.props.onEdit(role, {[e.target.name]: e.target.value}) 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 _ from 'lodash'
import classnames from 'classnames' 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 RoleRow from 'src/admin/components/RoleRow'
import EmptyRow from 'src/admin/components/EmptyRow' import EmptyRow from 'src/admin/components/EmptyRow'
import FilterBar from 'src/admin/components/FilterBar' import FilterBar from 'src/admin/components/FilterBar'
@ -17,7 +18,7 @@ const RolesTable = ({
onUpdateRoleUsers, onUpdateRoleUsers,
onUpdateRolePermissions, onUpdateRolePermissions,
}) => }) =>
<div className="panel panel-default"> <div className="panel panel-solid">
<FilterBar <FilterBar
type="roles" type="roles"
onFilter={onFilter} 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' import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserEditName extends Component { class UserEditName extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
} }
handleKeyPress(user) { handleKeyPress = user => {
return e => { return e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.props.onSave(user) this.props.onSave(user)
@ -18,7 +16,7 @@ class UserEditName extends Component {
} }
} }
handleEdit(user) { handleEdit = user => {
return e => { return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value}) 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' import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserNewPassword extends Component { class UserNewPassword extends Component {
constructor(props) { handleKeyPress = user => {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
return e => { return e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.props.onSave(user) this.props.onSave(user)
@ -18,7 +12,7 @@ class UserNewPassword extends Component {
} }
} }
handleEdit(user) { handleEdit = user => {
return e => { return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value}) 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 _ from 'lodash'
import classnames from 'classnames' 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 UserRow from 'src/admin/components/UserRow'
import EmptyRow from 'src/admin/components/EmptyRow' import EmptyRow from 'src/admin/components/EmptyRow'
@ -20,7 +21,7 @@ const UsersTable = ({
onUpdateRoles, onUpdateRoles,
onUpdatePassword, onUpdatePassword,
}) => }) =>
<div className="panel panel-default"> <div className="panel panel-solid">
<FilterBar <FilterBar
type="users" type="users"
onFilter={onFilter} onFilter={onFilter}

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react' import React from 'react'
import PropTypes from 'prop-types'
import { import {
isUserAuthorized, 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 AllUsersTableHeader from 'src/admin/components/chronograf/AllUsersTableHeader'
import AllUsersTableRowNew from 'src/admin/components/chronograf/AllUsersTableRowNew' import AllUsersTableRowNew from 'src/admin/components/chronograf/AllUsersTableRowNew'
@ -15,6 +16,11 @@ const {
colActions, colActions,
} = ALL_USERS_TABLE } = ALL_USERS_TABLE
import {
NOTIFY_CHRONOGRAF_USER_ADDED_TO_ORG,
NOTIFY_CHRONOGRAF_USER_REMOVED_FROM_ORG,
} from 'shared/copy/notifications'
class AllUsersTable extends Component { class AllUsersTable extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -46,7 +52,7 @@ class AllUsersTable extends Component {
this.props.onUpdateUserRoles( this.props.onUpdateUserRoles(
user, user,
newRoles, 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( this.props.onUpdateUserRoles(
user, user,
newRoles, 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, onCreateUser,
authConfig, authConfig,
meID, meID,
notify,
onDeleteUser, onDeleteUser,
isLoading, isLoading,
} = this.props } = this.props
@ -91,7 +96,7 @@ class AllUsersTable extends Component {
const {isCreatingUser} = this.state const {isCreatingUser} = this.state
if (isLoading) { if (isLoading) {
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-body"> <div className="panel-body">
<div className="page-spinner" /> <div className="page-spinner" />
</div> </div>
@ -99,7 +104,7 @@ class AllUsersTable extends Component {
) )
} }
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<AllUsersTableHeader <AllUsersTableHeader
numUsers={users.length} numUsers={users.length}
numOrganizations={organizations.length} numOrganizations={organizations.length}
@ -153,7 +158,6 @@ class AllUsersTable extends Component {
organizations={organizations} organizations={organizations}
onBlur={this.handleBlurCreateUserRow} onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser} onCreateUser={onCreateUser}
notify={notify}
/> />
: null} : null}
</tbody> </tbody>
@ -208,7 +212,6 @@ AllUsersTable.propTypes = {
superAdminNewUsers: bool, superAdminNewUsers: bool,
}), }),
meID: string.isRequired, meID: string.isRequired,
notify: func.isRequired,
isLoading: bool.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' import SlideToggle from 'shared/components/SlideToggle'
@ -17,7 +18,7 @@ const AllUsersTableHeader = ({
: 's'}` : 's'}`
return ( return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{numUsersString} across {numOrganizationsString} {numUsersString} across {numOrganizationsString}
</h2> </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 Tags from 'shared/components/Tags'
import SlideToggle from 'shared/components/SlideToggle' 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 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' import {ALL_USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const { const {
colOrganizations, colOrganizations,
@ -79,8 +84,7 @@ class AllUsersTableRowNew extends Component {
if (e.key === 'Enter') { if (e.key === 'Enter') {
if (preventCreate) { if (preventCreate) {
return this.props.notify( return this.props.notify(
'warning', NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER
'User must have a name and provider'
) )
} }
this.handleConfirmCreateUser() this.handleConfirmCreateUser()
@ -180,4 +184,8 @@ AllUsersTableRowNew.propTypes = {
notify: func.isRequired, 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 OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew' import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
@ -45,7 +46,7 @@ class OrganizationsTable extends Component {
if (!organizations.length) { if (!organizations.length) {
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-body"> <div className="panel-body">
<div className="page-spinner" /> <div className="page-spinner" />
</div> </div>
@ -53,8 +54,8 @@ class OrganizationsTable extends Component {
) )
} }
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{tableTitle} {tableTitle}
</h2> </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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router' import {withRouter} from 'react-router'
@ -94,7 +95,7 @@ class OrganizationsTableRow extends Component {
<InputClickToEdit <InputClickToEdit
value={organization.name} value={organization.name}
wrapperClass="fancytable--td orgs-table--name" wrapperClass="fancytable--td orgs-table--name"
onUpdate={this.handleUpdateOrgName} onBlur={this.handleUpdateOrgName}
/> />
<div className={defaultRoleClassName}> <div className={defaultRoleClassName}>
<Dropdown <Dropdown
@ -111,6 +112,7 @@ class OrganizationsTableRow extends Component {
onConfirm={this.handleDeleteOrg} onConfirm={this.handleDeleteOrg}
onClickOutside={this.handleDismissDeleteConfirmation} onClickOutside={this.handleDismissDeleteConfirmation}
confirmLeft={true} confirmLeft={true}
confirmTitle="Delete"
/> />
: <OrganizationsTableRowDeleteButton : <OrganizationsTableRowDeleteButton
organization={organization} 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 ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown' 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 ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow'
import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew' import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew'
@ -43,7 +44,7 @@ class ProvidersTable extends Component {
if (isLoading) { if (isLoading) {
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-body"> <div className="panel-body">
<div className="page-spinner" /> <div className="page-spinner" />
</div> </div>
@ -52,8 +53,8 @@ class ProvidersTable extends Component {
} }
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{tableTitle} {tableTitle}
</h2> </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 ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown' import Dropdown from 'shared/components/Dropdown'
@ -80,14 +81,14 @@ class ProvidersTableRow extends Component {
<InputClickToEdit <InputClickToEdit
value={provider} value={provider}
wrapperClass="fancytable--td provider--provider" wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider} onBlur={this.handleChangeProvider}
disabled={isDefaultMapping} disabled={isDefaultMapping}
tabIndex={rowIndex} tabIndex={rowIndex}
/> />
<InputClickToEdit <InputClickToEdit
value={providerOrganization} value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg" wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg} onBlur={this.handleChangeProviderOrg}
disabled={isDefaultMapping} disabled={isDefaultMapping}
tabIndex={rowIndex} tabIndex={rowIndex}
/> />
@ -109,6 +110,7 @@ class ProvidersTableRow extends Component {
onCancel={this.handleDismissDeleteConfirmation} onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteMap} onConfirm={this.handleDeleteMap}
onClickOutside={this.handleDismissDeleteConfirmation} onClickOutside={this.handleDismissDeleteConfirmation}
confirmTitle="Delete"
/> />
: <button : <button
className="btn btn-sm btn-default btn-square" 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 ConfirmButtons from 'src/shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown' import Dropdown from 'src/shared/components/Dropdown'
import InputClickToEdit from 'shared/components/InputClickToEdit' 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) { constructor(props) {
super(props) super(props)
@ -14,25 +38,31 @@ class ProvidersTableRowNew extends Component {
providerOrganization: null, providerOrganization: null,
organizationId: 'default', 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}) this.setState({scheme: scheme.text})
} }
handleChangeProvider = provider => { handleChangeProvider(provider: string) {
this.setState({provider}) this.setState({provider})
} }
handleChangeProviderOrg = providerOrganization => { handleChangeProviderOrg(providerOrganization: string) {
this.setState({providerOrganization}) this.setState({providerOrganization})
} }
handleChooseOrganization = org => { handleChooseOrganization(org: Organization) {
this.setState({organizationId: org.id}) this.setState({organizationId: org.id})
} }
handleSaveNewMapping = () => { handleSaveNewMapping() {
const {onCreate} = this.props const {onCreate} = this.props
onCreate(this.state) onCreate(this.state)
} }
@ -62,14 +92,16 @@ class ProvidersTableRowNew extends Component {
<InputClickToEdit <InputClickToEdit
value={provider} value={provider}
wrapperClass="fancytable--td provider--provider" wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider} onChange={this.handleChangeProvider}
onBlur={this.handleChangeProvider}
tabIndex={rowIndex} tabIndex={rowIndex}
placeholder="google" placeholder="google"
/> />
<InputClickToEdit <InputClickToEdit
value={providerOrganization} value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg" wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg} onChange={this.handleChangeProviderOrg}
onBlur={this.handleChangeProviderOrg}
tabIndex={rowIndex} tabIndex={rowIndex}
placeholder="*" 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 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 UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew' import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'
@ -34,21 +35,14 @@ class UsersTable extends Component {
} }
render() { render() {
const { const {organization, users, onCreateUser, meID, isLoading} = this.props
organization,
users,
onCreateUser,
meID,
notify,
isLoading,
} = this.props
const {isCreatingUser} = this.state const {isCreatingUser} = this.state
const {colRole, colProvider, colScheme, colActions} = USERS_TABLE const {colRole, colProvider, colScheme, colActions} = USERS_TABLE
if (isLoading) { if (isLoading) {
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<div className="panel-body"> <div className="panel-body">
<div className="page-spinner" /> <div className="page-spinner" />
</div> </div>
@ -56,7 +50,7 @@ class UsersTable extends Component {
) )
} }
return ( return (
<div className="panel panel-default"> <div className="panel panel-solid">
<UsersTableHeader <UsersTableHeader
numUsers={users.length} numUsers={users.length}
onClickCreateUser={this.handleClickCreateUser} onClickCreateUser={this.handleClickCreateUser}
@ -82,7 +76,6 @@ class UsersTable extends Component {
organization={organization} organization={organization}
onBlur={this.handleBlurCreateUserRow} onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser} onCreateUser={onCreateUser}
notify={notify}
/> />
: null} : null}
{users.length {users.length
@ -137,7 +130,6 @@ UsersTable.propTypes = {
onUpdateUserRole: func.isRequired, onUpdateUserRole: func.isRequired,
onDeleteUser: func.isRequired, onDeleteUser: func.isRequired,
meID: string.isRequired, meID: string.isRequired,
notify: func.isRequired,
isLoading: bool.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 { class UsersTableHeader extends Component {
constructor(props) { constructor(props) {
@ -16,7 +17,7 @@ class UsersTableHeader extends Component {
const panelTitle = numUsers === 1 ? `${numUsers} User` : `${numUsers} Users` const panelTitle = numUsers === 1 ? `${numUsers} User` : `${numUsers} Users`
return ( return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{panelTitle} in <em>{organization.name}</em> {panelTitle} in <em>{organization.name}</em>
</h2> </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 Dropdown from 'shared/components/Dropdown'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell' 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 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 {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin' import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
@ -60,8 +66,7 @@ class UsersTableRowNew extends Component {
if (e.key === 'Enter') { if (e.key === 'Enter') {
if (preventCreate) { if (preventCreate) {
return this.props.notify( return this.props.notify(
'warning', NOTIFY_CHRONOGRAF_USER_MISSING_NAME_AND_PROVIDER
'User must have a name and provider'
) )
} }
this.handleConfirmCreateUser() this.handleConfirmCreateUser()
@ -147,4 +152,8 @@ UsersTableRowNew.propTypes = {
notify: func.isRequired, 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import { import {
@ -28,7 +29,12 @@ import AdminTabs from 'src/admin/components/AdminTabs'
import SourceIndicator from 'shared/components/SourceIndicator' import SourceIndicator from 'shared/components/SourceIndicator'
import FancyScrollbar from 'shared/components/FancyScrollbar' 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 isValidUser = user => {
const minLen = 3 const minLen = 3
@ -74,7 +80,7 @@ class AdminInfluxDBPage extends Component {
handleSaveUser = async user => { handleSaveUser = async user => {
const {notify} = this.props const {notify} = this.props
if (!isValidUser(user)) { if (!isValidUser(user)) {
notify('error', 'Username and/or password too short') notify(NOTIFY_DB_USER_NAME_PASSWORD_INVALID)
return return
} }
if (user.isNew) { if (user.isNew) {
@ -87,7 +93,7 @@ class AdminInfluxDBPage extends Component {
handleSaveRole = async role => { handleSaveRole = async role => {
const {notify} = this.props const {notify} = this.props
if (!isValidRole(role)) { if (!isValidRole(role)) {
notify('error', 'Role name too short') notify(NOTIFY_ROLE_NAME_INVALID)
return return
} }
if (role.isNew) { if (role.isNew) {
@ -228,7 +234,7 @@ AdminInfluxDBPage.propTypes = {
updateUserPermissions: func, updateUserPermissions: func,
updateUserRoles: func, updateUserRoles: func,
updateUserPassword: func, updateUserPassword: func,
notify: func, notify: func.isRequired,
} }
const mapStateToProps = ({adminInfluxDB: {users, roles, permissions}}) => ({ const mapStateToProps = ({adminInfluxDB: {users, roles, permissions}}) => ({
@ -264,7 +270,7 @@ const mapDispatchToProps = dispatch => ({
), ),
updateUserRoles: bindActionCreators(updateUserRolesAsync, dispatch), updateUserRoles: bindActionCreators(updateUserRolesAsync, dispatch),
updateUserPassword: bindActionCreators(updateUserPasswordAsync, dispatch), updateUserPassword: bindActionCreators(updateUserPasswordAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(AdminInfluxDBPage) 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import _ from 'lodash' import _ from 'lodash'
@ -6,7 +7,13 @@ import _ from 'lodash'
import DatabaseManager from 'src/admin/components/DatabaseManager' import DatabaseManager from 'src/admin/components/DatabaseManager'
import * as adminActionCreators from 'src/admin/actions/influxdb' 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 { class DatabaseManagerPage extends Component {
constructor(props) { constructor(props) {
@ -34,11 +41,11 @@ class DatabaseManagerPage extends Component {
handleCreateDatabase = database => { handleCreateDatabase = database => {
const {actions, notify, source, databases} = this.props const {actions, notify, source, databases} = this.props
if (!database.name) { 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) { 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) actions.createDatabaseAsync(source.links.databases, database)
@ -59,11 +66,11 @@ class DatabaseManagerPage extends Component {
if (key === 'Enter') { if (key === 'Enter') {
if (!database.name) { 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) { 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) actions.createDatabaseAsync(source.links.databases, database)
@ -80,7 +87,9 @@ class DatabaseManagerPage extends Component {
if (key === 'Enter') { if (key === 'Enter') {
if (database.deleteCode !== `DELETE ${database.name}`) { 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) return actions.deleteDatabaseAsync(database)
@ -152,7 +161,7 @@ DatabaseManagerPage.propTypes = {
removeRetentionPolicy: func, removeRetentionPolicy: func,
deleteRetentionPolicyAsync: func, deleteRetentionPolicyAsync: func,
}), }),
notify: func, notify: func.isRequired,
} }
const mapStateToProps = ({adminInfluxDB: {databases, retentionPolicies}}) => ({ const mapStateToProps = ({adminInfluxDB: {databases, retentionPolicies}}) => ({
@ -162,7 +171,7 @@ const mapStateToProps = ({adminInfluxDB: {databases, retentionPolicies}}) => ({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminActionCreators, dispatch), actions: bindActionCreators(adminActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(DatabaseManagerPage) 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf' 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' import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable'
@ -95,7 +96,7 @@ const mapStateToProps = ({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch), actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(ProvidersPage) 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
@ -11,21 +12,17 @@ import QueriesTable from 'src/admin/components/QueriesTable'
import showDatabasesParser from 'shared/parsing/showDatabases' import showDatabasesParser from 'shared/parsing/showDatabases'
import showQueriesParser from 'shared/parsing/showQueries' import showQueriesParser from 'shared/parsing/showQueries'
import {TIMES} from 'src/admin/constants' import {TIMES} from 'src/admin/constants'
import {NOTIFY_QUERIES_ERROR} from 'shared/copy/notifications'
import { import {
loadQueries as loadQueriesAction, loadQueries as loadQueriesAction,
setQueryToKill as setQueryToKillAction, setQueryToKill as setQueryToKillAction,
killQueryAsync, killQueryAsync,
} from 'src/admin/actions/influxdb' } from 'src/admin/actions/influxdb'
import {publishAutoDismissingNotification} from 'shared/dispatchers' import {notify as notifyAction} from 'shared/actions/notifications'
class QueriesPage extends Component { class QueriesPage extends Component {
constructor(props) {
super(props)
this.updateQueries = ::this.updateQueries
this.handleKillQuery = ::this.handleKillQuery
}
componentDidMount() { componentDidMount() {
this.updateQueries() this.updateQueries()
const updateInterval = 5000 const updateInterval = 5000
@ -42,12 +39,12 @@ class QueriesPage extends Component {
return <QueriesTable queries={queries} onKillQuery={this.handleKillQuery} /> return <QueriesTable queries={queries} onKillQuery={this.handleKillQuery} />
} }
updateQueries() { updateQueries = () => {
const {source, notify, loadQueries} = this.props const {source, notify, loadQueries} = this.props
showDatabases(source.links.proxy).then(resp => { showDatabases(source.links.proxy).then(resp => {
const {databases, errors} = showDatabasesParser(resp.data) const {databases, errors} = showDatabasesParser(resp.data)
if (errors.length) { if (errors.length) {
errors.forEach(message => notify('error', message)) errors.forEach(message => notify(NOTIFY_QUERIES_ERROR(message)))
return return
} }
@ -58,7 +55,9 @@ class QueriesPage extends Component {
queryResponses.forEach(queryResponse => { queryResponses.forEach(queryResponse => {
const result = showQueriesParser(queryResponse.data) const result = showQueriesParser(queryResponse.data)
if (result.errors.length) { if (result.errors.length) {
result.errors.forEach(message => notify('error', message)) result.errors.forEach(message =>
notify(NOTIFY_QUERIES_ERROR(message))
)
} }
allQueries.push(...result.queries) allQueries.push(...result.queries)
@ -78,7 +77,7 @@ class QueriesPage extends Component {
}) })
} }
handleKillQuery(id) { handleKillQuery = id => {
const {source, killQuery} = this.props const {source, killQuery} = this.props
killQuery(source.links.proxy, id) killQuery(source.links.proxy, id)
} }
@ -97,7 +96,7 @@ QueriesPage.propTypes = {
queryIDToKill: string, queryIDToKill: string,
setQueryToKill: func, setQueryToKill: func,
killQuery: func, killQuery: func,
notify: func, notify: func.isRequired,
} }
const mapStateToProps = ({adminInfluxDB: {queries, queryIDToKill}}) => ({ const mapStateToProps = ({adminInfluxDB: {queries, queryIDToKill}}) => ({
@ -109,7 +108,7 @@ const mapDispatchToProps = dispatch => ({
loadQueries: bindActionCreators(loadQueriesAction, dispatch), loadQueries: bindActionCreators(loadQueriesAction, dispatch),
setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch), setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch),
killQuery: bindActionCreators(killQueryAsync, dispatch), killQuery: bindActionCreators(killQueryAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(QueriesPage) 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 {connect} from 'react-redux'
import AdminTabs from 'src/admin/components/chronograf/AdminTabs' 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf' import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import * as configActionCreators from 'shared/actions/config' import * as configActionCreators from 'src/shared/actions/config'
import {publishAutoDismissingNotification} from 'shared/dispatchers' import {notify as notifyAction} from 'src/shared/actions/notifications'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable' 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) { constructor(props) {
super(props) super(props)
@ -22,32 +52,6 @@ class AllUsersPage extends Component {
getAuthConfigAsync(links.config.auth) 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() { async componentWillMount() {
const { const {
links, links,
@ -64,65 +68,66 @@ class AllUsersPage extends Component {
this.setState({isLoading: false}) 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() { render() {
const { const {
organizations,
meID, meID,
users, users,
authConfig,
actionsConfig,
links, links,
notify, notify,
authConfig,
actionsConfig,
organizations,
} = this.props } = this.props
return ( return (
<AllUsersTable <AllUsersTable
meID={meID} meID={meID}
users={users} users={users}
links={links}
notify={notify}
authConfig={authConfig}
actionsConfig={actionsConfig}
organizations={organizations} organizations={organizations}
isLoading={this.state.isLoading}
onDeleteUser={this.handleDeleteUser}
onCreateUser={this.handleCreateUser} onCreateUser={this.handleCreateUser}
onUpdateUserRoles={this.handleUpdateUserRoles} onUpdateUserRoles={this.handleUpdateUserRoles}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin} 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 = ({ const mapStateToProps = ({
links, links,
adminChronograf: {organizations, users}, adminChronograf: {organizations, users},
@ -137,7 +142,7 @@ const mapStateToProps = ({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
actionsAdmin: bindActionCreators(adminChronografActionCreators, dispatch), actionsAdmin: bindActionCreators(adminChronografActionCreators, dispatch),
actionsConfig: bindActionCreators(configActionCreators, dispatch), actionsConfig: bindActionCreators(configActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(AllUsersPage) 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 {connect} from 'react-redux'
import {bindActionCreators} from '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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf' 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' import UsersTable from 'src/admin/components/chronograf/UsersTable'
@ -115,7 +116,7 @@ const mapStateToProps = ({links, adminChronograf: {organizations, users}}) => ({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch), actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch), notify: bindActionCreators(notifyAction, dispatch),
}) })
export default connect(mapStateToProps, mapDispatchToProps)(UsersPage) export default connect(mapStateToProps, mapDispatchToProps)(UsersPage)

View File

@ -5,7 +5,7 @@ import {
NEW_DEFAULT_DATABASE, NEW_DEFAULT_DATABASE,
NEW_EMPTY_RP, NEW_EMPTY_RP,
} from 'src/admin/constants' } from 'src/admin/constants'
import uuid from 'node-uuid' import uuid from 'uuid'
const initialState = { const initialState = {
users: null, 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 _ from 'lodash'
import classnames from 'classnames' import classnames from 'classnames'
import {Link} from 'react-router' import {Link} from 'react-router'
import uuid from 'node-uuid' import uuid from 'uuid'
import InfiniteScroll from 'shared/components/InfiniteScroll' import InfiniteScroll from 'shared/components/InfiniteScroll'
@ -220,8 +221,8 @@ class AlertsTable extends Component {
</button> </button>
: null} : null}
</div> </div>
: <div className="panel panel-minimal"> : <div className="panel">
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{this.props.alerts.length} Alerts {this.props.alerts.length} Alerts
</h2> </h2>
@ -243,9 +244,6 @@ class SearchBar extends Component {
this.state = { this.state = {
searchTerm: '', searchTerm: '',
} }
this.handleSearch = ::this.handleSearch
this.handleChange = ::this.handleChange
} }
componentWillMount() { componentWillMount() {
@ -253,27 +251,25 @@ class SearchBar extends Component {
this.handleSearch = _.debounce(this.handleSearch, waitPeriod) this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
} }
handleSearch() { handleSearch = () => {
this.props.onSearch(this.state.searchTerm) this.props.onSearch(this.state.searchTerm)
} }
handleChange(e) { handleChange = e => {
this.setState({searchTerm: e.target.value}, this.handleSearch) this.setState({searchTerm: e.target.value}, this.handleSearch)
} }
render() { render() {
return ( return (
<div className="users__search-widget input-group"> <div className="search-widget" style={{width: '260px'}}>
<input <input
type="text" type="text"
className="form-control" className="form-control input-sm"
placeholder="Filter Alerts..." placeholder="Filter Alerts..."
onChange={this.handleChange} onChange={this.handleChange}
value={this.state.searchTerm} value={this.state.searchTerm}
/> />
<div className="input-group-addon"> <span className="icon search" />
<span className="icon search" />
</div>
</div> </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 SourceIndicator from 'shared/components/SourceIndicator'
import AlertsTable from 'src/alerts/components/AlertsTable' import AlertsTable from 'src/alerts/components/AlertsTable'
@ -11,7 +12,7 @@ import AJAX from 'utils/ajax'
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import timeRanges from 'hson!shared/data/timeRanges.hson' import {timeRanges} from 'shared/data/timeRanges'
class AlertsApp extends Component { class AlertsApp extends Component {
constructor(props) { constructor(props) {
@ -35,11 +36,6 @@ class AlertsApp extends Component {
limitMultiplier: 1, // only used if AlertsApp receives a limit prop limitMultiplier: 1, // only used if AlertsApp receives a limit prop
isAlertsMaxedOut: false, // 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 // 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( getAlerts(
this.props.source.links.proxy, this.props.source.links.proxy,
this.state.timeRange, this.state.timeRange,
@ -112,13 +108,13 @@ class AlertsApp extends Component {
}) })
} }
handleGetMoreAlerts() { handleGetMoreAlerts = () => {
this.setState({limitMultiplier: this.state.limitMultiplier + 1}, () => { this.setState({limitMultiplier: this.state.limitMultiplier + 1}, () => {
this.fetchAlerts(this.state.limitMultiplier) this.fetchAlerts(this.state.limitMultiplier)
}) })
} }
renderSubComponents() { renderSubComponents = () => {
const {source, isWidget, limit} = this.props const {source, isWidget, limit} = this.props
const {isAlertsMaxedOut, alerts} = this.state const {isAlertsMaxedOut, alerts} = this.state
@ -135,7 +131,7 @@ class AlertsApp extends Component {
: <NoKapacitorError source={source} /> : <NoKapacitorError source={source} />
} }
handleApplyTime(timeRange) { handleApplyTime = timeRange => {
this.setState({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 {connect} from 'react-redux'
import {withRouter} from 'react-router' import {withRouter} from 'react-router'

View File

@ -1,5 +1,6 @@
/* global VERSION */ /* global VERSION */
import React, {PropTypes} from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Notifications from 'shared/components/Notifications' 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 {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router' 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' import {isUserAuthorized, VIEWER_ROLE} from 'src/auth/Authorized'

View File

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

View File

@ -8,10 +8,14 @@ import {
runTemplateVariableQuery, runTemplateVariableQuery,
} from 'src/dashboards/apis' } from 'src/dashboards/apis'
import {publishAutoDismissingNotification} from 'shared/dispatchers' import {notify} from 'shared/actions/notifications'
import {errorThrown} from 'shared/actions/errors' import {errorThrown} from 'shared/actions/errors'
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants' import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants'
import {
NOTIFY_DASHBOARD_DELETED,
NOTIFY_DASHBOARD_DELETE_FAILED,
} from 'shared/copy/notifications'
import { import {
TEMPLATE_VARIABLE_SELECTED, TEMPLATE_VARIABLE_SELECTED,
@ -257,15 +261,13 @@ export const deleteDashboardAsync = dashboard => async dispatch => {
dispatch(deleteDashboard(dashboard)) dispatch(deleteDashboard(dashboard))
try { try {
await deleteDashboardAJAX(dashboard) await deleteDashboardAJAX(dashboard)
dispatch( dispatch(notify(NOTIFY_DASHBOARD_DELETED(dashboard.name)))
publishAutoDismissingNotification(
'success',
'Dashboard deleted successfully.'
)
)
} catch (error) { } catch (error) {
dispatch( dispatch(
errorThrown(error, `Failed to delete dashboard: ${error.data.message}.`) errorThrown(
error,
NOTIFY_DASHBOARD_DELETE_FAILED(dashboard.name, error.data.message)
)
) )
dispatch(deleteDashboardFailed(dashboard)) 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 {connect} from 'react-redux'
import {bindActionCreators} from '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 {Tabber, Tab} from 'src/dashboards/components/Tabber'
import FancyScrollbar from 'shared/components/FancyScrollbar' 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 {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay' 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) const getInputMin = scale => (scale === LOG ? '0' : null)
class AxesOptions extends Component { class AxesOptions extends Component {
@ -90,7 +94,7 @@ class AxesOptions extends Component {
<h5 className="display-options--header"> <h5 className="display-options--header">
{menuOption} Controls {menuOption} Controls
</h5> </h5>
<form autoComplete="off" style={{margin: '0 -6px'}}> <form autoComplete="off" className="form-group-wrapper">
<div className="form-group col-sm-12"> <div className="form-group col-sm-12">
<label htmlFor="prefix">Title</label> <label htmlFor="prefix">Title</label>
<OptIn <OptIn
@ -139,7 +143,7 @@ class AxesOptions extends Component {
<Tabber <Tabber
labelText="Y-Value's Format" labelText="Y-Value's Format"
tipID="Y-Values's Format" tipID="Y-Values's Format"
tipContent={TOOLTIP_CONTENT.FORMAT} tipContent={TOOLTIP_Y_VALUE_FORMAT}
> >
<Tab <Tab
text="K/M/B" 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 _ 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 QueryMaker from 'src/dashboards/components/QueryMaker'
import Visualization from 'src/dashboards/components/Visualization' import Visualization from 'src/dashboards/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls' import OverlayControls from 'src/dashboards/components/OverlayControls'
@ -20,10 +21,10 @@ import {
removeUnselectedTemplateValues, removeUnselectedTemplateValues,
TYPE_QUERY_CONFIG, TYPE_QUERY_CONFIG,
} from 'src/dashboards/constants' } 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 {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'shared/constants' import {AUTO_GROUP_BY} from 'src/shared/constants'
import {stringifyColorValues} from 'src/dashboards/constants/gaugeColors' import {stringifyColorValues} from 'src/shared/constants/colorOperations'
class CellEditorOverlay extends Component { class CellEditorOverlay extends Component {
constructor(props) { constructor(props) {
@ -105,7 +106,7 @@ class CellEditorOverlay extends Component {
handleSaveCell = () => { handleSaveCell = () => {
const {queriesWorkingDraft, staticLegend} = this.state const {queriesWorkingDraft, staticLegend} = this.state
const {cell, singleStatColors, gaugeColors} = this.props const {cell, thresholdsListColors, gaugeColors} = this.props
const queries = queriesWorkingDraft.map(q => { const queries = queriesWorkingDraft.map(q => {
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
@ -119,13 +120,18 @@ class CellEditorOverlay extends Component {
}) })
let colors = [] let colors = []
if (cell.type === 'gauge') {
colors = stringifyColorValues(gaugeColors) switch (cell.type) {
} else if ( case 'gauge': {
cell.type === 'single-stat' || colors = stringifyColorValues(gaugeColors)
cell.type === 'line-plus-single-stat' break
) { }
colors = stringifyColorValues(singleStatColors) case 'single-stat':
case 'line-plus-single-stat':
case 'table': {
colors = stringifyColorValues(thresholdsListColors)
break
}
} }
this.props.onSave({ this.props.onSave({
@ -374,8 +380,8 @@ CellEditorOverlay.propTypes = {
}).isRequired, }).isRequired,
dashboardID: string.isRequired, dashboardID: string.isRequired,
sources: arrayOf(shape()), sources: arrayOf(shape()),
singleStatType: string.isRequired, thresholdsListType: string.isRequired,
singleStatColors: arrayOf(shape({}).isRequired).isRequired, thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: 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 classnames from 'classnames'
import TemplateControlBar from 'src/dashboards/components/TemplateControlBar' import TemplateControlBar from 'src/dashboards/components/TemplateControlBar'
@ -15,7 +16,6 @@ const Dashboard = ({
autoRefresh, autoRefresh,
manualRefresh, manualRefresh,
onDeleteCell, onDeleteCell,
synchronizer,
onPositionChange, onPositionChange,
inPresentationMode, inPresentationMode,
onOpenTemplateManager, onOpenTemplateManager,
@ -25,6 +25,8 @@ const Dashboard = ({
showTemplateControlBar, showTemplateControlBar,
setScrollTop, setScrollTop,
inView, inView,
onSetHoverTime,
hoverTime,
}) => { }) => {
const cells = dashboard.cells.map(cell => { const cells = dashboard.cells.map(cell => {
const dashboardCell = { const dashboardCell = {
@ -65,7 +67,8 @@ const Dashboard = ({
timeRange={timeRange} timeRange={timeRange}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
manualRefresh={manualRefresh} manualRefresh={manualRefresh}
synchronizer={synchronizer} hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
onDeleteCell={onDeleteCell} onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange} onPositionChange={onPositionChange}
templates={templatesIncludingDashTime} templates={templatesIncludingDashTime}
@ -111,7 +114,8 @@ Dashboard.propTypes = {
onPositionChange: func, onPositionChange: func,
onDeleteCell: func, onDeleteCell: func,
onSummonOverlayTechnologies: func, onSummonOverlayTechnologies: func,
synchronizer: func, hoverTime: string,
onSetHoverTime: func,
source: shape({ source: shape({
links: shape({ links: shape({
proxy: string, 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 classnames from 'classnames'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' 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 { import {
DASHBOARD_NAME_MAX_LENGTH, DASHBOARD_NAME_MAX_LENGTH,
NEW_DASHBOARD, 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 {Link} from 'react-router'
import _ from 'lodash' import _ from 'lodash'
import classnames from 'classnames' 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' import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
@ -45,12 +46,12 @@ class DashboardsPageContents extends Component {
<div className="container-fluid"> <div className="container-fluid">
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
<div className="panel panel-minimal"> <div className="panel">
<div className="panel-heading u-flex u-ai-center u-jc-space-between"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">
{tableHeader} {tableHeader}
</h2> </h2>
<div className="u-flex u-ai-center dashboards-page--actions"> <div className="dashboards-page--actions">
<SearchBar <SearchBar
placeholder="Filter by Name..." placeholder="Filter by Name..."
onSearch={this.filterDashboards} 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 {Link} from 'react-router'
import _ from 'lodash' import _ from 'lodash'

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