Merge remote-tracking branch 'origin/master' into fun/dashboard_page

pull/3739/head
Jared Scheib 2018-06-20 14:22:57 -07:00
commit 1dc2976597
145 changed files with 3255 additions and 2369 deletions

138
.circleci/config.yml Normal file
View File

@ -0,0 +1,138 @@
workflows:
version: 2
main:
jobs:
- build
- deploy-nightly:
requires:
- build
filters:
branches:
only: master
- deploy-pre-release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^[0-9]+(\.[0-9]+)*(\S*)([a|rc|beta]([0-9]+))+$/
- deploy-release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^[0-9]+(\.[0-9]+)*$/
version: 2
jobs:
build:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- checkout
- run: |
ls -lah
pwd
- run: ./etc/scripts/docker/pull.sh
- run:
name: "Run Tests"
command: >
./etc/scripts/docker/run.sh
--debug
--test
--no-build
- persist_to_workspace:
root: /home/circleci
paths:
- project
deploy-nightly:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--debug \
--clean \
--package \
--platform all \
--arch all \
--upload \
--nightly \
--bucket=dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:nightly
docker push quay.io/influxdb/chronograf:nightly
- store_artifacts:
path: ./build/
deploy-pre-release:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--clean \
--debug \
--release \
--package \
--platform all \
--arch all \
--upload-overwrite \
--upload \
--bucket dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- store_artifacts:
path: ./build/
deploy-release:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--clean \
--debug \
--release \
--package \
--platform all \
--arch all \
--upload-overwrite \
--upload \
--bucket dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker tag chronograf quay.io/influxdb/chronograf:latest
docker push quay.io/influxdb/chronograf:latest
- store_artifacts:
path: ./build/

View File

@ -10,6 +10,7 @@
1. [#3474](https://github.com/influxdata/chronograf/pull/3474): Sort task table on Manage Alert page alphabetically
1. [#3590](https://github.com/influxdata/chronograf/pull/3590): Redesign icons in side navigation
1. [#3696](https://github.com/influxdata/chronograf/pull/3696): Add ability to delete entire queries in Flux Editor
1. [#3671](https://github.com/influxdata/chronograf/pull/3671): Remove Snip functionality in hover legend
1. [#3659](https://github.com/influxdata/chronograf/pull/3659): Upgrade Data Explorer query text field with syntax highlighting and partial multi-line support
1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table
@ -21,6 +22,7 @@
1. [#3649](https://github.com/influxdata/chronograf/pull/3649): Fix erroneous icons in Date Picker widget
1. [#3697](https://github.com/influxdata/chronograf/pull/3697): Fix allowing hyphens in basepath
1. [#3698](https://github.com/influxdata/chronograf/pull/3698): Fix error in cell when tempVar returns no values
1. [#3733](https://github.com/influxdata/chronograf/pull/3733): Change arrows in table columns so that ascending sort points up and descending points down
## v1.5.0.0 [2018-05-15-RC]

View File

@ -341,6 +341,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Selected: v.Selected,
Type: v.Type,
Value: v.Value,
Key: v.Key,
}
}
@ -522,6 +523,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
Selected: v.Selected,
Type: v.Type,
Value: v.Value,
Key: v.Key,
}
}

View File

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

View File

@ -100,6 +100,7 @@ message TemplateValue {
string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
string value = 2; // Value is the specific value used to replace a template in an InfluxQL query
bool selected = 3; // Selected states that this variable has been picked to use for replacement
string key = 4; // Key is the key for a specific Value if the Template Type is map (optional)
}
message TemplateQuery {

View File

@ -158,9 +158,10 @@ type Range struct {
// TemplateValue is a value use to replace a template in an InfluxQL query
type TemplateValue struct {
Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query
Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement
Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query
Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant, influxql
Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement
Key string `json:"key,omitempty"` // Key is the key for the Value if the Template Type is 'map'
}
// TemplateVar is a named variable within an InfluxQL query to be replaced with Values
@ -176,7 +177,7 @@ type TemplateID string
type Template struct {
TemplateVar
ID TemplateID `json:"id"` // ID is the unique ID associated with this template
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, measurements, databases, map, influxql
Label string `json:"label"` // Label is a user-facing description of the Template
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
}

View File

@ -1,91 +0,0 @@
---
machine:
services:
- docker
environment:
DOCKER_TAG: chronograf-20180327
dependencies:
override:
- ./etc/scripts/docker/pull.sh
test:
override:
- >
./etc/scripts/docker/run.sh
--debug
--test
--no-build
deployment:
master:
branch: master
commands:
- >
./etc/scripts/docker/run.sh
--debug
--clean
--package
--platform all
--arch all
--upload
--nightly
--bucket=dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:nightly
- docker push quay.io/influxdb/chronograf:nightly
- mv ./build/* $CIRCLE_ARTIFACTS
pre-release:
tag: /^[0-9]+(\.[0-9]+)*(\S*)([a|rc|beta]([0-9]+))+$/
commands:
- >
./etc/scripts/docker/run.sh
--clean
--debug
--release
--package
--platform all
--arch all
--upload-overwrite
--upload
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- mv ./build/* $CIRCLE_ARTIFACTS
release:
tag: /^[0-9]+(\.[0-9]+)*$/
commands:
- >
./etc/scripts/docker/run.sh
--clean
--debug
--release
--package
--platform all
--arch all
--upload-overwrite
--upload
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker tag chronograf quay.io/influxdb/chronograf:latest
- docker push quay.io/influxdb/chronograf:latest
- mv ./build/* $CIRCLE_ARTIFACTS

View File

@ -72,7 +72,7 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
return strings.Replace(q, t.Var, `"`+t.Values[0].Value+`"`, -1), nil
case "tagValue", "timeStamp":
return strings.Replace(q, t.Var, `'`+t.Values[0].Value+`'`, -1), nil
case "csv", "constant":
case "csv", "constant", "influxql":
return strings.Replace(q, t.Var, t.Values[0].Value, -1), nil
}

View File

@ -4090,6 +4090,10 @@
"enum": ["csv", "tagKey", "tagValue", "fieldKey", "timeStamp"],
"description":
"The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv and timeStamp are not quoted."
},
"key": {
"type": "string",
"description":"This will be the key for a specific value of a template variable. Used if the templateVar type is 'map'"
}
}
},

View File

@ -15,19 +15,23 @@ func ValidTemplateRequest(template *chronograf.Template) error {
switch template.Type {
default:
return fmt.Errorf("Unknown template type %s", template.Type)
case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases":
case "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map", "influxql":
}
for _, v := range template.Values {
switch v.Type {
default:
return fmt.Errorf("Unknown template variable type %s", v.Type)
case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant":
case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant", "influxql":
}
if template.Type == "map" && v.Key == "" {
return fmt.Errorf("Templates of type 'map' require a 'key'")
}
}
if template.Type == "query" && template.Query == nil {
return fmt.Errorf("No query set for template of type 'query'")
if template.Type == "influxql" && template.Query == nil {
return fmt.Errorf("No query set for template of type 'influxql'")
}
return nil

View File

@ -57,7 +57,37 @@ func TestValidTemplateRequest(t *testing.T) {
name: "No query set",
wantErr: true,
template: &chronograf.Template{
Type: "query",
Type: "influxql",
},
},
{
name: "Valid Map type",
template: &chronograf.Template{
Type: "map",
TemplateVar: chronograf.TemplateVar{
Values: []chronograf.TemplateValue{
{
Key: "key",
Value: "value",
Type: "constant",
},
},
},
},
},
{
name: "Map without Key",
wantErr: true,
template: &chronograf.Template{
Type: "map",
TemplateVar: chronograf.TemplateVar{
Values: []chronograf.TemplateValue{
{
Value: "value",
Type: "constant",
},
},
},
},
},
}

View File

@ -29,7 +29,7 @@ import UsersTable from 'src/admin/components/UsersTable'
import RolesTable from 'src/admin/components/RolesTable'
import QueriesPage from 'src/admin/containers/QueriesPage'
import DatabaseManagerPage from 'src/admin/containers/DatabaseManagerPage'
import SourceIndicator from 'shared/components/SourceIndicator'
import PageHeader from 'shared/components/PageHeader'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import SubSections from 'shared/components/SubSections'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -223,16 +223,7 @@ class AdminInfluxDBPage extends Component {
return (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">InfluxDB Admin</h1>
</div>
<div className="page-header__right">
<SourceIndicator />
</div>
</div>
</div>
<PageHeader title="InfluxDB Admin" sourceIndicator={true} />
<FancyScrollbar className="page-contents">
{users ? (
<div className="container-fluid">

View File

@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import PageHeader from 'src/shared/components/PageHeader'
import SubSections from 'src/shared/components/SubSections'
import FancyScrollbar from 'shared/components/FancyScrollbar'
@ -49,13 +50,7 @@ const sections = me => [
const AdminChronografPage = ({me, source, params: {tab}}) => (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Chronograf Admin</h1>
</div>
</div>
</div>
<PageHeader title="Chronograf Admin" />
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<SubSections

View File

@ -48,7 +48,7 @@ interface State {
@ErrorHandling
export class AllUsersPage extends PureComponent<Props, State> {
constructor(props) {
constructor(props: Props) {
super(props)
this.state = {

View File

@ -1,5 +1,5 @@
import {proxy} from 'src/utils/queryUrlGenerator'
import {TimeRange} from '../../types'
import {TimeRange} from 'src/types'
export const getAlerts = (
source: string,

View File

@ -1,9 +1,9 @@
import React, {PureComponent} from 'react'
import SourceIndicator from 'src/shared/components/SourceIndicator'
import AlertsTable from 'src/alerts/components/AlertsTable'
import NoKapacitorError from 'src/shared/components/NoKapacitorError'
import CustomTimeRangeDropdown from 'src/shared/components/CustomTimeRangeDropdown'
import PageHeader from 'src/shared/components/PageHeader'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {getAlerts} from 'src/alerts/apis'
@ -85,7 +85,7 @@ class AlertsApp extends PureComponent<Props, State> {
}
public render() {
const {isWidget, source} = this.props
const {loading, timeRange} = this.state
const {loading} = this.state
if (loading || !source) {
return <div className="page-spinner" />
@ -95,20 +95,11 @@ class AlertsApp extends PureComponent<Props, State> {
this.renderSubComponents()
) : (
<div className="page alert-history-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Alert History</h1>
</div>
<div className="page-header__right">
<SourceIndicator />
<CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange}
/>
</div>
</div>
</div>
<PageHeader
titleText="Alert History"
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
@ -120,6 +111,17 @@ class AlertsApp extends PureComponent<Props, State> {
)
}
private get optionsComponents(): JSX.Element {
const {timeRange} = this.state
return (
<CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange}
/>
)
}
private fetchAlerts = (): void => {
getAlerts(
this.props.source.links.proxy,

View File

@ -3,7 +3,9 @@ import {replace} from 'react-router-redux'
import _ from 'lodash'
import queryString from 'query-string'
import {proxy} from 'src/utils/queryUrlGenerator'
import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
import {parseMetaQuery} from 'src/tempVars/utils/parsing'
import {
getDashboards as getDashboardsAJAX,
@ -13,7 +15,6 @@ import {
updateDashboardCell as updateDashboardCellAJAX,
addDashboardCell as addDashboardCellAJAX,
deleteDashboardCell as deleteDashboardCellAJAX,
getTempVarValuesBySourceQuery,
createDashboard as createDashboardAJAX,
} from 'src/dashboards/apis'
import {getMe} from 'src/shared/apis/auth'
@ -46,7 +47,6 @@ import {
} from 'src/shared/copy/notifications'
import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars'
import parsers from 'src/shared/parsing'
import {getDeep} from 'src/utils/wrappers'
import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
@ -444,7 +444,7 @@ export const getDashboardsNamesAsync = (
}
}
export const getDashboardAsync = (dashboardID: string) => async (
export const getDashboardAsync = (dashboardID: number) => async (
dispatch
): Promise<Dashboard | null> => {
try {
@ -690,25 +690,23 @@ export const hydrateTempVarValuesAsync = (
const dashboard = getState().dashboardUI.dashboards.find(
d => d.id === dashboardID
)
const templates: Template[] = dashboard.templates
const queries = templates
.filter(
template => getDeep<string>(template, 'query.influxql', '') !== ''
)
.map(async template => {
const query = makeQueryForTemplate(template.query)
const response = await proxy({source: source.links.proxy, query})
const values = parseMetaQuery(query, response.data)
const tempsWithQueries = dashboard.templates.filter(
({query}) => !!query.influxql
)
const asyncQueries = tempsWithQueries.map(({query}) =>
getTempVarValuesBySourceQuery(source, {
query: makeQueryForTemplate(query),
return {template, values}
})
)
const results = await Promise.all(queries)
const results = await Promise.all(asyncQueries)
results.forEach(({data}, i) => {
const {type, query, id} = tempsWithQueries[i]
const parsed = parsers[type](data, query.tagKey || query.measurement)
const vals = parsed[type]
dispatch(editTemplateVariableValues(+dashboard.id, id, vals))
})
for (const {template, values} of results) {
dispatch(editTemplateVariableValues(+dashboard.id, template.id, values))
}
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
@ -946,7 +944,7 @@ type GetDashboardWithHydratedAndSyncedTempVarsAsyncActionCreator = (
) => Promise<void>
export const getDashboardWithHydratedAndSyncedTempVarsAsync = (
dashboardID: string,
dashboardID: number,
source: Source,
router: InjectedRouter,
location: Location

View File

@ -1,5 +1,4 @@
import AJAX from 'utils/ajax'
import {proxy} from 'utils/queryUrlGenerator'
export function getDashboards() {
return AJAX({
@ -98,19 +97,3 @@ export const editTemplateVariables = async templateVariable => {
throw error
}
}
export const getTempVarValuesBySourceQuery = async (source, templateQuery) => {
const {
query,
db,
// rp, TODO
tempVars,
} = templateQuery
try {
// TODO: add rp as argument to proxy
return await proxy({source: source.links.proxy, query, db, tempVars})
} catch (error) {
console.error(error)
throw error
}
}

View File

@ -1,147 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
import SourceIndicator from 'shared/components/SourceIndicator'
import GraphTips from 'shared/components/GraphTips'
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
import DashboardSwitcher from 'src/dashboards/components/DashboardSwitcher'
const DashboardHeader = ({
names,
onSave,
onCancel,
isEditMode,
isHidden,
dashboard,
onAddCell,
autoRefresh,
activeDashboard,
onEditDashboard,
onManualRefresh,
handleChooseTimeRange,
handleChooseAutoRefresh,
onToggleTempVarControls,
showTemplateControlBar,
timeRange: {upper, lower},
handleClickPresentationButton,
zoomedTimeRange: {lower: zoomedLower, upper: zoomedUpper},
}) =>
isHidden ? null : (
<div className="page-header full-width">
<div className="page-header__container">
<div
className={
dashboard
? 'page-header__left page-header__dash-editable'
: 'page-header__left'
}
>
{names && names.length > 1 ? (
<DashboardSwitcher
names={names}
activeDashboard={activeDashboard}
/>
) : null}
{dashboard ? (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={
<h1 className="page-header__title">{activeDashboard}</h1>
}
>
<DashboardHeaderEdit
onSave={onSave}
onCancel={onCancel}
activeDashboard={activeDashboard}
onEditDashboard={onEditDashboard}
isEditMode={isEditMode}
/>
</Authorized>
) : (
<h1 className="page-header__title">{activeDashboard}</h1>
)}
</div>
<div className="page-header__right">
<GraphTips />
<SourceIndicator />
{dashboard ? (
<Authorized requiredRole={EDITOR_ROLE}>
<button className="btn btn-primary btn-sm" onClick={onAddCell}>
<span className="icon plus" />
Add Cell
</button>
</Authorized>
) : null}
{dashboard ? (
<div
className={classnames('btn btn-default btn-sm', {
active: showTemplateControlBar,
})}
onClick={onToggleTempVarControls}
>
<span className="icon cube" />Template Variables
</div>
) : null}
<AutoRefreshDropdown
onChoose={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
selected={autoRefresh}
iconName="refresh"
/>
<TimeRangeDropdown
onChooseTimeRange={handleChooseTimeRange}
selected={{
upper: zoomedUpper || upper,
lower: zoomedLower || lower,
}}
/>
<div
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
>
<span className="icon expand-a" />
</div>
</div>
</div>
</div>
)
const {arrayOf, bool, func, number, shape, string} = PropTypes
DashboardHeader.defaultProps = {
zoomedTimeRange: {
lower: null,
upper: null,
},
}
DashboardHeader.propTypes = {
activeDashboard: string.isRequired,
onEditDashboard: func,
dashboard: shape({}),
timeRange: shape({
lower: string,
upper: string,
}).isRequired,
autoRefresh: number.isRequired,
isHidden: bool.isRequired,
isEditMode: bool,
handleChooseTimeRange: func.isRequired,
handleChooseAutoRefresh: func.isRequired,
onManualRefresh: func.isRequired,
handleClickPresentationButton: func.isRequired,
onAddCell: func,
onToggleTempVarControls: func,
showTemplateControlBar: bool,
zoomedTimeRange: shape({}),
onCancel: func,
onSave: func,
names: arrayOf(shape({})).isRequired,
}
export default DashboardHeader

View File

@ -0,0 +1,189 @@
import React, {Component} from 'react'
import classnames from 'classnames'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import PageHeader from 'src/shared/components/PageHeader'
import PageHeaderTitle from 'src/shared/components/PageHeaderTitle'
import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import GraphTips from 'src/shared/components/GraphTips'
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
import DashboardSwitcher from 'src/dashboards/components/DashboardSwitcher'
import {Dashboard, TimeRange} from 'src/types'
interface DashboardName {
text: string
}
interface Props {
activeDashboard: string
dashboard: Dashboard
onEditDashboard: () => void
timeRange: TimeRange
autoRefresh: number
isEditMode?: boolean
handleChooseTimeRange: (timeRange: TimeRange) => void
handleChooseAutoRefresh: () => void
onManualRefresh: () => void
handleClickPresentationButton: () => void
onAddCell: () => void
onToggleTempVarControls: () => void
showTemplateControlBar: boolean
zoomedTimeRange: TimeRange
onCancel: () => void
onSave: () => void
names: DashboardName[]
isHidden: boolean
}
class DashboardHeader extends Component<Props> {
public static defaultProps: Partial<Props> = {
zoomedTimeRange: {
upper: null,
lower: null,
},
}
public render() {
const {isHidden} = this.props
return (
<PageHeader
fullWidth={true}
sourceIndicator={true}
titleComponents={this.renderPageTitle}
optionsComponents={this.optionsComponents}
inPresentationMode={isHidden}
/>
)
}
private get renderPageTitle(): JSX.Element {
return (
<>
{this.dashboardSwitcher}
{this.dashboardTitle}
</>
)
}
private get optionsComponents(): JSX.Element {
const {
handleChooseAutoRefresh,
onManualRefresh,
autoRefresh,
handleChooseTimeRange,
timeRange: {upper, lower},
zoomedTimeRange: {upper: zoomedUpper, lower: zoomedLower},
handleClickPresentationButton,
} = this.props
return (
<>
<GraphTips />
{this.addCellButton}
{this.tempVarsButton}
<AutoRefreshDropdown
onChoose={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
selected={autoRefresh}
iconName="refresh"
/>
<TimeRangeDropdown
onChooseTimeRange={handleChooseTimeRange}
selected={{
upper: zoomedUpper || upper,
lower: zoomedLower || lower,
}}
/>
<button
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
>
<span className="icon expand-a" />
</button>
</>
)
}
private get addCellButton(): JSX.Element {
const {dashboard, onAddCell} = this.props
if (dashboard) {
return (
<Authorized requiredRole={EDITOR_ROLE}>
<button className="btn btn-primary btn-sm" onClick={onAddCell}>
<span className="icon plus" />
Add Cell
</button>
</Authorized>
)
}
}
private get tempVarsButton(): JSX.Element {
const {
dashboard,
showTemplateControlBar,
onToggleTempVarControls,
} = this.props
if (dashboard) {
return (
<div
className={classnames('btn btn-default btn-sm', {
active: showTemplateControlBar,
})}
onClick={onToggleTempVarControls}
>
<span className="icon cube" />Template Variables
</div>
)
}
}
private get dashboardSwitcher(): JSX.Element {
const {names, activeDashboard} = this.props
if (names && names.length > 1) {
return (
<DashboardSwitcher names={names} activeDashboard={activeDashboard} />
)
}
}
private get dashboardTitle(): JSX.Element {
const {
dashboard,
activeDashboard,
onSave,
onCancel,
onEditDashboard,
isEditMode,
} = this.props
if (dashboard) {
return (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={
<PageHeaderTitle title={activeDashboard} />
}
>
<DashboardHeaderEdit
onSave={onSave}
onCancel={onCancel}
activeDashboard={activeDashboard}
onEditDashboard={onEditDashboard}
isEditMode={isEditMode}
/>
</Authorized>
)
}
return <PageHeaderTitle title={activeDashboard} />
}
}
export default DashboardHeader

View File

@ -1,18 +0,0 @@
import React from 'react'
import SourceIndicator from 'src/shared/components/SourceIndicator'
const DashboardsHeader = (): JSX.Element => (
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Dashboards</h1>
</div>
<div className="page-header__right">
<SourceIndicator />
</div>
</div>
</div>
)
export default DashboardsHeader

View File

@ -12,7 +12,7 @@ import {
applyMasks,
insertTempVar,
unMask,
} from 'src/dashboards/constants'
} from 'src/tempVars/constants'
@ErrorHandling
class QueryTextArea extends Component {

View File

@ -4,8 +4,8 @@ import {connect} from 'react-redux'
import download from 'src/external/download'
import _ from 'lodash'
import DashboardsHeader from 'src/dashboards/components/DashboardsHeader'
import DashboardsContents from 'src/dashboards/components/DashboardsPageContents'
import PageHeader from 'src/shared/components/PageHeader'
import {createDashboard} from 'src/dashboards/apis'
import {
@ -54,7 +54,7 @@ class DashboardsPage extends PureComponent<Props> {
return (
<div className="page">
<DashboardsHeader />
<PageHeader titleText="Dashboards" sourceIndicator={true} />
<DashboardsContents
dashboardLink={dashboardLink}
dashboards={dashboards}

View File

@ -32,7 +32,7 @@ export const generateURLQueryParamsFromTempVars = (
const selected = values.find(value => value.selected === true)
const strippedTempVar = stripTempVar(tempVar)
urlQueryParams[strippedTempVar] = selected.value
urlQueryParams[strippedTempVar] = _.get(selected, 'value', '')
})
return urlQueryParams

View File

@ -24,8 +24,8 @@ class WriteDataHeader extends PureComponent<Props> {
return (
<div className="write-data-form--header">
<div className="page-header__left">
<h1 className="page-header__title">Write Data To</h1>
<div className="page-header--left">
<h1 className="page-header--title">Write Data To</h1>
<DatabaseDropdown
source={source}
onSelectDatabase={handleSelectDatabase}
@ -45,7 +45,7 @@ class WriteDataHeader extends PureComponent<Props> {
</li>
</ul>
</div>
<div className="page-header__right">
<div className="page-header--right">
<span className="page-header__dismiss" onClick={onClose} />
</div>
</div>

View File

@ -12,10 +12,13 @@ import {stripPrefix} from 'src/utils/basepath'
import QueryMaker from 'src/data_explorer/components/QueryMaker'
import Visualization from 'src/data_explorer/components/Visualization'
import WriteDataForm from 'src/data_explorer/components/WriteDataForm'
import Header from 'src/data_explorer/containers/Header'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import OverlayTechnologies from 'src/shared/components/OverlayTechnologies'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import GraphTips from 'src/shared/components/GraphTips'
import PageHeader from 'src/shared/components/PageHeader'
import {VIS_VIEWS, AUTO_GROUP_BY, TEMPLATES} from 'src/shared/constants'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
@ -93,11 +96,9 @@ export class DataExplorer extends PureComponent<Props, State> {
autoRefresh,
queryConfigs,
manualRefresh,
onManualRefresh,
errorThrownAction,
writeLineProtocol,
queryConfigActions,
handleChooseAutoRefresh,
} = this.props
const {showWriteForm} = this.state
@ -115,13 +116,11 @@ export class DataExplorer extends PureComponent<Props, State> {
/>
</OverlayTechnologies>
) : null}
<Header
timeRange={timeRange}
autoRefresh={autoRefresh}
showWriteForm={this.handleOpenWriteData}
onChooseTimeRange={this.handleChooseTimeRange}
onChooseAutoRefresh={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
<PageHeader
titleText="Data Explorer"
fullWidth={true}
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
<ResizeContainer
containerClass="page-contents"
@ -186,6 +185,40 @@ export class DataExplorer extends PureComponent<Props, State> {
const {timeRange} = this.props
return buildRawText(this.activeQuery, timeRange)
}
private get optionsComponents(): JSX.Element {
const {
timeRange,
autoRefresh,
onManualRefresh,
handleChooseAutoRefresh,
} = this.props
return (
<>
<GraphTips />
<div
className="btn btn-sm btn-default"
onClick={this.handleOpenWriteData}
data-test="write-data-button"
>
<span className="icon pencil" />
Write Data
</div>
<AutoRefreshDropdown
iconName="refresh"
selected={autoRefresh}
onChoose={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
/>
<TimeRangeDropdown
selected={timeRange}
page="DataExplorer"
onChooseTimeRange={this.handleChooseTimeRange}
/>
</>
)
}
}
const mapStateToProps = state => {

View File

@ -1,63 +0,0 @@
import React, {PureComponent} from 'react'
import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import SourceIndicator from 'src/shared/components/SourceIndicator'
import GraphTips from 'src/shared/components/GraphTips'
import {TimeRange} from 'src/types'
interface Props {
onChooseAutoRefresh: () => void
onManualRefresh: () => void
onChooseTimeRange: (timeRange: TimeRange) => void
showWriteForm: () => void
autoRefresh: number
timeRange: TimeRange
}
class Header extends PureComponent<Props> {
public render() {
const {
timeRange,
autoRefresh,
showWriteForm,
onManualRefresh,
onChooseTimeRange,
onChooseAutoRefresh,
} = this.props
return (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Data Explorer</h1>
</div>
<div className="page-header__right">
<GraphTips />
<SourceIndicator />
<div
className="btn btn-sm btn-default"
onClick={showWriteForm}
data-test="write-data-button"
>
<span className="icon pencil" />
Write Data
</div>
<AutoRefreshDropdown
iconName="refresh"
selected={autoRefresh}
onChoose={onChooseAutoRefresh}
onManualRefresh={onManualRefresh}
/>
<TimeRangeDropdown
selected={timeRange}
page="DataExplorer"
onChooseTimeRange={onChooseTimeRange}
/>
</div>
</div>
</div>
)
}
}
export default Header

View File

@ -3,7 +3,10 @@ import _ from 'lodash'
import AJAX from 'src/utils/ajax'
import {Service, FluxTable} from 'src/types'
import {updateService} from 'src/shared/apis'
import {parseResponse} from 'src/shared/parsing/flux/response'
import {
parseResponse,
parseResponseError,
} from 'src/shared/parsing/flux/response'
import {MAX_RESPONSE_BYTES} from 'src/flux/constants'
export const getSuggestions = async (url: string) => {
@ -56,6 +59,9 @@ export const getTimeSeries = async (
service.links.proxy
}?path=/v1/query${mark}orgName=defaulorgname${and}q=${garbage}`
let responseBody: string
let responseByteLength: number
try {
// We are using the `fetch` API here since the `AJAX` utility lacks support
// for limiting response size. The `AJAX` utility depends on
@ -66,16 +72,28 @@ export const getTimeSeries = async (
const resp = await fetch(url, {method: 'POST'})
const {body, byteLength} = await decodeFluxRespWithLimit(resp)
return {
tables: parseResponse(body),
didTruncate: byteLength >= MAX_RESPONSE_BYTES,
}
responseBody = body
responseByteLength = byteLength
} catch (error) {
console.error('Problem fetching data', error)
throw _.get(error, 'headers.x-influx-error', false) ||
_.get(error, 'data.message', 'unknown error 🤷')
}
try {
return {
tables: parseResponse(responseBody),
didTruncate: responseByteLength >= MAX_RESPONSE_BYTES,
}
} catch (error) {
console.error('Could not parse response body', error)
return {
tables: parseResponseError(responseBody),
didTruncate: false,
}
}
}
export const updateScript = async (service: Service, script: string) => {

View File

@ -3,12 +3,13 @@ import _ from 'lodash'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ExpressionNode from 'src/flux/components/ExpressionNode'
import VariableName from 'src/flux/components/VariableName'
import VariableNode from 'src/flux/components/VariableNode'
import FuncSelector from 'src/flux/components/FuncSelector'
import BodyDelete from 'src/flux/components/BodyDelete'
import {funcNames} from 'src/flux/constants'
import {Service} from 'src/types'
import {FlatBody, Suggestion} from 'src/types/flux'
import {Body, Suggestion} from 'src/types/flux'
interface Props {
service: Service
@ -16,21 +17,25 @@ interface Props {
suggestions: Suggestion[]
onAppendFrom: () => void
onAppendJoin: () => void
}
interface Body extends FlatBody {
id: string
onDeleteBody: (bodyID: string) => void
}
class BodyBuilder extends PureComponent<Props> {
public render() {
const bodybuilder = this.props.body.map((b, i) => {
const {body, onDeleteBody} = this.props
const bodybuilder = body.map((b, i) => {
if (b.declarations.length) {
return b.declarations.map(d => {
if (d.funcs) {
return (
<div className="declaration" key={i}>
<VariableName name={d.name} assignedToQuery={true} />
<div className="func-node--wrapper">
<VariableNode name={d.name} assignedToQuery={true} />
<div className="func-node--menu">
<BodyDelete bodyID={b.id} onDeleteBody={onDeleteBody} />
</div>
</div>
<ExpressionNode
bodyID={b.id}
declarationID={d.id}
@ -38,6 +43,7 @@ class BodyBuilder extends PureComponent<Props> {
funcs={d.funcs}
declarationsFromBody={this.declarationsFromBody}
isLastBody={this.isLastBody(i)}
onDeleteBody={onDeleteBody}
/>
</div>
)
@ -45,7 +51,16 @@ class BodyBuilder extends PureComponent<Props> {
return (
<div className="declaration" key={i}>
<VariableName name={b.source} assignedToQuery={false} />
<div className="func-node--wrapper">
<VariableNode name={b.source} assignedToQuery={false} />
<div className="func-node--menu">
<BodyDelete
bodyID={b.id}
type="variable"
onDeleteBody={onDeleteBody}
/>
</div>
</div>
</div>
)
})
@ -59,6 +74,7 @@ class BodyBuilder extends PureComponent<Props> {
funcNames={this.funcNames}
declarationsFromBody={this.declarationsFromBody}
isLastBody={this.isLastBody(i)}
onDeleteBody={onDeleteBody}
/>
</div>
)

View File

@ -0,0 +1,49 @@
import React, {PureComponent} from 'react'
import ConfirmButton from 'src/shared/components/ConfirmButton'
type BodyType = 'variable' | 'query'
interface Props {
bodyID: string
type?: BodyType
onDeleteBody: (bodyID: string) => void
}
class BodyDelete extends PureComponent<Props> {
public static defaultProps: Partial<Props> = {
type: 'query',
}
public render() {
const {type} = this.props
if (type === 'variable') {
return (
<button
className="btn btn-sm btn-square btn-danger"
title="Delete Variable"
onClick={this.handleDelete}
>
<span className="icon remove" />
</button>
)
}
return (
<ConfirmButton
icon="trash"
type="btn-danger"
confirmText="Delete Query"
square={true}
confirmAction={this.handleDelete}
position="right"
/>
)
}
private handleDelete = (): void => {
this.props.onDeleteBody(this.props.bodyID)
}
}
export default BodyDelete

View File

@ -1,12 +1,12 @@
import React, {PureComponent, Fragment} from 'react'
import _ from 'lodash'
import {FluxContext} from 'src/flux/containers/FluxPage'
import FuncSelector from 'src/flux/components/FuncSelector'
import FuncNode from 'src/flux/components/FuncNode'
import YieldFuncNode from 'src/flux/components/YieldFuncNode'
import {getDeep} from 'src/utils/wrappers'
import {Func} from 'src/types/flux'
import {Func, Context} from 'src/types/flux'
interface Props {
funcNames: any[]
@ -15,13 +15,13 @@ interface Props {
declarationID?: string
declarationsFromBody: string[]
isLastBody: boolean
onDeleteBody: (bodyID: string) => void
}
interface State {
nonYieldableIndexesToggled: {
[x: number]: boolean
}
isImplicitYieldToggled: boolean
}
// an Expression is a group of one or more functions
@ -31,7 +31,6 @@ class ExpressionNode extends PureComponent<Props, State> {
this.state = {
nonYieldableIndexesToggled: {},
isImplicitYieldToggled: this.isImplicitYieldToggled,
}
}
@ -42,6 +41,7 @@ class ExpressionNode extends PureComponent<Props, State> {
funcNames,
funcs,
declarationsFromBody,
onDeleteBody,
} = this.props
const {nonYieldableIndexesToggled} = this.state
@ -57,13 +57,17 @@ class ExpressionNode extends PureComponent<Props, State> {
service,
data,
scriptUpToYield,
}) => {
}: Context) => {
let isAfterRange = false
let isAfterFilter = false
return (
<>
{funcs.map((func, i) => {
if (func.name === 'yield') {
return null
}
if (func.name === 'range') {
isAfterRange = true
}
@ -72,22 +76,7 @@ class ExpressionNode extends PureComponent<Props, State> {
isAfterFilter = true
}
if (func.name === 'yield') {
const script = scriptUpToYield(bodyID, declarationID, i, true)
return (
<YieldFuncNode
index={i}
key={i}
func={func}
data={data}
script={script}
bodyID={bodyID}
service={service}
declarationID={declarationID}
/>
)
}
const isYieldable = isAfterFilter && isAfterRange
const funcNode = (
<FuncNode
@ -100,21 +89,26 @@ class ExpressionNode extends PureComponent<Props, State> {
onChangeArg={onChangeArg}
onDelete={onDeleteFuncNode}
onToggleYield={onToggleYield}
isYieldable={isAfterFilter && isAfterRange}
isYielding={this.isBeforeFuncYield(i)}
isYieldable={isYieldable}
isYielding={this.isBeforeYielding(i)}
isYieldedInScript={this.isYieldNodeIndex(i + 1)}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
declarationsFromBody={declarationsFromBody}
onToggleYieldWithLast={this.handleToggleYieldWithLast}
onDeleteBody={onDeleteBody}
/>
)
if (nonYieldableIndexesToggled[i]) {
const script = scriptUpToYield(
if (
nonYieldableIndexesToggled[i] ||
this.isYieldNodeIndex(i + 1)
) {
const script: string = scriptUpToYield(
bodyID,
declarationID,
i,
false
isYieldable
)
return (
@ -131,42 +125,9 @@ class ExpressionNode extends PureComponent<Props, State> {
/>
</Fragment>
)
} else if (this.isEndOfScript(i)) {
const script = scriptUpToYield(bodyID, declarationID, i, true)
return (
<Fragment key={`${i}-notInScript`}>
<FuncNode
key={i}
index={i}
func={func}
funcs={funcs}
bodyID={bodyID}
service={service}
onChangeArg={onChangeArg}
onDelete={onDeleteFuncNode}
onToggleYield={this.handleHideImplicitYield}
isYieldable={isAfterFilter && isAfterRange}
isYielding={this.isBeforeFuncYield(i)}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
declarationsFromBody={declarationsFromBody}
onToggleYieldWithLast={this.handleToggleYieldWithLast}
/>
<YieldFuncNode
index={i}
func={func}
data={data}
script={script}
bodyID={bodyID}
service={service}
declarationID={declarationID}
/>
</Fragment>
)
} else {
return funcNode
}
return funcNode
})}
<FuncSelector
bodyID={bodyID}
@ -181,45 +142,26 @@ class ExpressionNode extends PureComponent<Props, State> {
)
}
private isBeforeFuncYield(funcIndex: number): boolean {
const {funcs, isLastBody} = this.props
const {isImplicitYieldToggled} = this.state
private isBeforeYielding(funcIndex: number): boolean {
const {nonYieldableIndexesToggled} = this.state
const beforeToggledLastYield = !!nonYieldableIndexesToggled[funcIndex]
if (
funcIndex === funcs.length - 1 &&
isLastBody &&
isImplicitYieldToggled
) {
if (beforeToggledLastYield) {
return true
}
if (funcIndex === funcs.length - 1) {
return false
}
const nextFunc = funcs[funcIndex + 1]
if (nextFunc.name === 'yield') {
return true
}
return false
return this.isYieldNodeIndex(funcIndex + 1)
}
private get isImplicitYieldToggled(): boolean {
const {isLastBody} = this.props
return isLastBody && this.isLastFuncYield
}
private get isLastFuncYield(): boolean {
private isYieldNodeIndex(funcIndex: number): boolean {
const {funcs} = this.props
const funcName = getDeep<string>(funcs, `${funcIndex}.name`, '')
return _.get(funcs, `${funcs.length - 1}.name`) !== 'yield'
return funcName === 'yield'
}
// if funcNode is not yieldable, add last before yield()
private handleToggleYieldWithLast = (funcNodeIndex: number) => {
private handleToggleYieldWithLast = (funcNodeIndex: number): void => {
this.setState(({nonYieldableIndexesToggled}) => {
const isFuncYieldToggled = !!nonYieldableIndexesToggled[funcNodeIndex]
@ -231,20 +173,6 @@ class ExpressionNode extends PureComponent<Props, State> {
}
})
}
private handleHideImplicitYield = () => {
this.setState(() => ({
isImplicitYieldToggled: false,
}))
}
private isEndOfScript(index: number): boolean {
const {isLastBody, funcs} = this.props
const {isImplicitYieldToggled} = this.state
const isLastScriptFunc = isLastBody && index === funcs.length - 1
return isLastScriptFunc && isImplicitYieldToggled
}
}
export default ExpressionNode

View File

@ -5,9 +5,11 @@ import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
import parseValuesColumn from 'src/shared/parsing/flux/values'
import FilterTagList from 'src/flux/components/FilterTagList'
import Walker from 'src/flux/ast/walker'
import {makeCancelable} from 'src/utils/promises'
import {Service} from 'src/types'
import {Links, OnChangeArg, Func, FilterNode} from 'src/types/flux'
import {WrappedCancelablePromise} from 'src/types/promises'
interface Props {
links: Links
@ -28,6 +30,8 @@ interface State {
}
class FilterArgs extends PureComponent<Props, State> {
private fetchTagKeysResponse?: WrappedCancelablePromise<string>
constructor(props) {
super(props)
this.state = {
@ -52,15 +56,24 @@ class FilterArgs extends PureComponent<Props, State> {
}
public async componentDidMount() {
const {db, service} = this.props
try {
this.convertStringToNodes()
const response = await fetchTagKeys(service, db, [])
const response = await this.getTagKeys()
const tagKeys = parseValuesColumn(response)
this.setState({tagKeys})
this.setState({
tagKeys,
})
} catch (error) {
console.error(error)
if (!error.isCanceled) {
console.error(error)
}
}
}
public componentWillUnmount() {
if (this.fetchTagKeysResponse) {
this.fetchTagKeysResponse.cancel()
}
}
@ -91,6 +104,14 @@ class FilterArgs extends PureComponent<Props, State> {
/>
)
}
private getTagKeys(): Promise<string> {
const {db, service} = this.props
this.fetchTagKeysResponse = makeCancelable(fetchTagKeys(service, db, []))
return this.fetchTagKeysResponse.promise
}
}
const mapStateToProps = ({links}) => {

View File

@ -3,6 +3,7 @@ import {connect} from 'react-redux'
import FluxOverlay from 'src/flux/components/FluxOverlay'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import PageHeader from 'src/shared/components/PageHeader'
import {
showOverlay as showOverlayAction,
ShowOverlay,
@ -18,18 +19,19 @@ interface Props {
class FluxHeader extends PureComponent<Props> {
public render() {
return (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Flux Editor</h1>
</div>
<div className="page-header__right">
<button onClick={this.overlay} className="btn btn-sm btn-default">
Edit Connection
</button>
</div>
</div>
</div>
<PageHeader
titleText="Flux Editor"
fullWidth={true}
optionsComponents={this.optionsComponents}
/>
)
}
private get optionsComponents(): JSX.Element {
return (
<button onClick={this.overlay} className="btn btn-sm btn-default">
Edit Connection
</button>
)
}

View File

@ -29,10 +29,10 @@ class FluxOverlay extends PureComponent<Props> {
return (
<div className="flux-overlay">
<div className="template-variable-manager--header">
<div className="page-header__left">
<h1 className="page-header__title">Connect to Flux</h1>
<div className="page-header--left">
<h1 className="page-header--title">Connect to Flux</h1>
</div>
<div className="page-header__right">
<div className="page-header--right">
<span
className="page-header__dismiss"
onClick={this.props.onDismiss}

View File

@ -8,7 +8,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
import FromDatabaseDropdown from 'src/flux/components/FromDatabaseDropdown'
import {funcNames, argTypes} from 'src/flux/constants'
import {OnChangeArg, Arg} from 'src/types/flux'
import {OnChangeArg, Arg, OnGenerateScript} from 'src/types/flux'
import {Service} from 'src/types'
interface Props {
@ -22,7 +22,7 @@ interface Props {
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
onGenerateScript: () => void
onGenerateScript: OnGenerateScript
}
@ErrorHandling

View File

@ -1,6 +1,6 @@
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeArg} from 'src/types/flux'
import {OnChangeArg, OnGenerateScript} from 'src/types/flux'
interface Props {
funcID: string
@ -10,7 +10,7 @@ interface Props {
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
onGenerateScript: () => void
onGenerateScript: OnGenerateScript
autoFocus?: boolean
}

View File

@ -2,7 +2,7 @@ import React, {PureComponent, ReactElement, MouseEvent} from 'react'
import FuncArg from 'src/flux/components/FuncArg'
import {OnChangeArg} from 'src/types/flux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Func} from 'src/types/flux'
import {Func, OnGenerateScript} from 'src/types/flux'
import {funcNames} from 'src/flux/constants'
import JoinArgs from 'src/flux/components/JoinArgs'
import FilterArgs from 'src/flux/components/FilterArgs'
@ -15,7 +15,7 @@ interface Props {
bodyID: string
onChangeArg: OnChangeArg
declarationID: string
onGenerateScript: () => void
onGenerateScript: OnGenerateScript
declarationsFromBody: string[]
onStopPropagation: (e: MouseEvent<HTMLElement>) => void
}

View File

@ -2,9 +2,11 @@ import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import BodyDelete from 'src/flux/components/BodyDelete'
import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
import {
OnGenerateScript,
OnDeleteFuncNode,
OnChangeArg,
OnToggleYield,
@ -23,11 +25,13 @@ interface Props {
onDelete: OnDeleteFuncNode
onToggleYield: OnToggleYield
onChangeArg: OnChangeArg
onGenerateScript: () => void
onGenerateScript: OnGenerateScript
onToggleYieldWithLast: (funcNodeIndex: number) => void
declarationsFromBody: string[]
isYielding: boolean
isYieldable: boolean
onDeleteBody: (bodyID: string) => void
isYieldedInScript: boolean
}
interface State {
@ -53,14 +57,16 @@ export default class FuncNode extends PureComponent<Props, State> {
return (
<>
<div
className={this.nodeClassName}
onClick={this.handleToggleEdit}
title="Edit function arguments"
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
<div className="func-node--wrapper">
<div
className={this.nodeClassName}
onClick={this.handleToggleEdit}
title="Edit function arguments"
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
</div>
{this.funcMenu}
</div>
{this.funcArgs}
@ -103,13 +109,7 @@ export default class FuncNode extends PureComponent<Props, State> {
return (
<div className="func-node--menu">
{this.yieldToggleButton}
<button
className="btn btn-sm btn-square btn-danger"
onClick={this.handleDelete}
title="Delete this Function"
>
<span className="icon trash" />
</button>
{this.deleteButton}
</div>
)
}
@ -140,6 +140,24 @@ export default class FuncNode extends PureComponent<Props, State> {
)
}
private get deleteButton(): JSX.Element {
const {func, bodyID, onDeleteBody} = this.props
if (func.name === 'from') {
return <BodyDelete onDeleteBody={onDeleteBody} bodyID={bodyID} />
}
return (
<button
className="btn btn-sm btn-square btn-danger"
onClick={this.handleDelete}
title="Delete this Function"
>
<span className="icon remove" />
</button>
)
}
private get nodeClassName(): string {
const {isYielding} = this.props
const {editing} = this.state
@ -147,14 +165,14 @@ export default class FuncNode extends PureComponent<Props, State> {
return classnames('func-node', {active: isYielding || editing})
}
private handleDelete = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
private handleDelete = (): void => {
const {func, bodyID, declarationID} = this.props
this.props.onDelete({funcID: func.id, bodyID, declarationID})
}
private handleToggleEdit = (): void => {
private handleToggleEdit = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
this.setState({editing: !this.state.editing})
}
@ -166,11 +184,12 @@ export default class FuncNode extends PureComponent<Props, State> {
index,
bodyID,
declarationID,
isYieldable,
onToggleYieldWithLast,
isYieldable,
isYieldedInScript,
} = this.props
if (isYieldable) {
if (isYieldedInScript || isYieldable) {
onToggleYield(bodyID, declarationID, index)
} else {
onToggleYieldWithLast(index)

View File

@ -7,6 +7,7 @@ import {
Suggestion,
OnChangeScript,
OnSubmitScript,
OnDeleteBody,
FlatBody,
ScriptStatus,
} from 'src/types/flux'
@ -22,6 +23,7 @@ interface Props {
status: ScriptStatus
suggestions: Suggestion[]
onChangeScript: OnChangeScript
onDeleteBody: OnDeleteBody
onSubmitScript: OnSubmitScript
onAppendFrom: () => void
onAppendJoin: () => void
@ -63,7 +65,14 @@ class TimeMachine extends PureComponent<Props> {
}
private get builder() {
const {body, service, suggestions, onAppendFrom, onAppendJoin} = this.props
const {
body,
service,
suggestions,
onAppendFrom,
onDeleteBody,
onAppendJoin,
} = this.props
return {
name: 'Build',
@ -75,6 +84,7 @@ class TimeMachine extends PureComponent<Props> {
body={body}
service={service}
suggestions={suggestions}
onDeleteBody={onDeleteBody}
onAppendFrom={onAppendFrom}
onAppendJoin={onAppendJoin}
/>

View File

@ -1,3 +1,19 @@
export const emptyAST = {
type: 'Program',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 1,
},
source: '',
},
body: [],
}
export const ast = {
type: 'File',
start: 0,

View File

@ -1,4 +1,4 @@
import {ast} from 'src/flux/constants/ast'
import {ast, emptyAST} from 'src/flux/constants/ast'
import * as editor from 'src/flux/constants/editor'
import * as argTypes from 'src/flux/constants/argumentTypes'
import * as funcNames from 'src/flux/constants/funcNames'
@ -10,6 +10,7 @@ const MAX_RESPONSE_BYTES = 1e7 // 10 MB
export {
ast,
emptyAST,
funcNames,
argTypes,
editor,

View File

@ -15,7 +15,7 @@ import {UpdateScript} from 'src/flux/actions'
import {bodyNodes} from 'src/flux/helpers'
import {getSuggestions, getAST, getTimeSeries} from 'src/flux/apis'
import {builder, argTypes} from 'src/flux/constants'
import {builder, argTypes, emptyAST} from 'src/flux/constants'
import {Source, Service, Notification, FluxTable} from 'src/types'
import {
@ -114,6 +114,7 @@ export class FluxPage extends PureComponent<Props, State> {
onAppendJoin={this.handleAppendJoin}
onChangeScript={this.handleChangeScript}
onSubmitScript={this.handleSubmitScript}
onDeleteBody={this.handleDeleteBody}
/>
</div>
</KeyboardShortcuts>
@ -331,12 +332,19 @@ export class FluxPage extends PureComponent<Props, State> {
this.getASTResponse(script)
}
private handleDeleteBody = (bodyID: string): void => {
const newBody = this.state.body.filter(b => b.id !== bodyID)
const script = this.getBodyToScript(newBody)
this.getASTResponse(script)
}
private handleScriptUpToYield = (
bodyID: string,
declarationID: string,
funcNodeIndex: number,
isYieldable: boolean
) => {
): string => {
const {body: bodies} = this.state
const bodyIndex = bodies.findIndex(b => b.id === bodyID)
@ -601,7 +609,8 @@ export class FluxPage extends PureComponent<Props, State> {
const {links} = this.props
if (!script) {
return
this.props.updateScript(script)
return this.setState({ast: emptyAST, body: []})
}
try {

View File

@ -5,9 +5,9 @@ import {bindActionCreators} from 'redux'
import _ from 'lodash'
import HostsTable from 'src/hosts/components/HostsTable'
import SourceIndicator from 'shared/components/SourceIndicator'
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import PageHeader from 'src/shared/components/PageHeader'
import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis'
import {getEnv} from 'src/shared/apis/env'
@ -110,31 +110,15 @@ export class HostsPage extends Component {
}
render() {
const {
source,
autoRefresh,
onChooseAutoRefresh,
onManualRefresh,
} = this.props
const {source} = this.props
const {hosts, hostsLoading, hostsError} = this.state
return (
<div className="page hosts-list-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Host List</h1>
</div>
<div className="page-header__right">
<SourceIndicator />
<AutoRefreshDropdown
iconName="refresh"
selected={autoRefresh}
onChoose={onChooseAutoRefresh}
onManualRefresh={onManualRefresh}
/>
</div>
</div>
</div>
<PageHeader
title="Host List"
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
@ -153,6 +137,19 @@ export class HostsPage extends Component {
)
}
optionsComponents = () => {
const {autoRefresh, onChooseAutoRefresh, onManualRefresh} = this.props
return (
<AutoRefreshDropdown
iconName="refresh"
selected={autoRefresh}
onChoose={onChooseAutoRefresh}
onManualRefresh={onManualRefresh}
/>
)
}
componentWillUnmount() {
clearInterval(this.intervalID)
this.intervalID = false

View File

@ -134,7 +134,7 @@ class AlertTabs extends PureComponent<Props, State> {
this.setState({services})
} catch (error) {
this.setState({services: null})
this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor))
this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor.name))
}
}

View File

@ -1,24 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const CodeData = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
const {func, shape, string} = PropTypes
CodeData.propTypes = {
onClickTemplate: func,
template: shape({
label: string,
text: string,
}),
}
export default CodeData

View File

@ -0,0 +1,20 @@
import React, {SFC} from 'react'
import {RuleMessage} from 'src/types/kapacitor'
interface Props {
onClickTemplate: () => void
template: RuleMessage
}
const CodeData: SFC<Props> = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
export default CodeData

View File

@ -24,7 +24,7 @@ interface Props {
query: QueryConfig
isDeadman: boolean
isKapacitorRule: boolean
onAddEvery: () => void
onAddEvery: (every?: string) => void
timeRange: TimeRange
}

View File

@ -1,13 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import {PERIODS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const periods = PERIODS.map(text => {
return {text}
})
const Deadman = ({rule, onChange}) => (
interface Item {
text: string
}
interface Props {
rule: AlertRule
onChange: (item: Item) => void
}
const Deadman: SFC<Props> = ({rule, onChange}) => (
<div className="rule-section--row rule-section--row-first rule-section--row-last">
<p>Send Alert if Data is missing for</p>
<Dropdown
@ -20,15 +31,4 @@ const Deadman = ({rule, onChange}) => (
</div>
)
const {shape, string, func} = PropTypes
Deadman.propTypes = {
rule: shape({
values: shape({
period: string,
}),
}),
onChange: func.isRequired,
}
export default Deadman

View File

@ -3,6 +3,7 @@ import React, {ChangeEvent, MouseEvent, PureComponent} from 'react'
import AlertOutputs from 'src/kapacitor/components/AlertOutputs'
import Input from 'src/kapacitor/components/KapacitorFormInput'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import PageHeader from 'src/shared/components/PageHeader'
import KapacitorFormSkipVerify from 'src/kapacitor/components/KapacitorFormSkipVerify'
import {Kapacitor, Source, Notification, NotificationFunc} from 'src/types'
@ -38,13 +39,7 @@ class KapacitorForm extends PureComponent<Props> {
return (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">{this.headerText}</h1>
</div>
</div>
</div>
<PageHeader titleText={this.headerText} />
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">

View File

@ -1,20 +1,21 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {InjectedRouter} from 'react-router'
import PageHeader from 'src/shared/components/PageHeader'
import NameSection from 'src/kapacitor/components/NameSection'
import ValuesSection from 'src/kapacitor/components/ValuesSection'
import RuleHeader from 'src/kapacitor/components/RuleHeader'
import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave'
import RuleHandlers from 'src/kapacitor/components/RuleHandlers'
import RuleMessage from 'src/kapacitor/components/RuleMessage'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql'
import {timeRanges} from 'shared/data/timeRanges'
import buildInfluxQLQuery from 'src/utils/influxql'
import {timeRanges} from 'src/shared/data/timeRanges'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {notify as notifyAction} from 'shared/actions/notifications'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {
notifyAlertRuleCreated,
@ -24,11 +25,52 @@ import {
notifyAlertRuleRequiresQuery,
notifyAlertRuleRequiresConditionValue,
notifyAlertRuleDeadmanInvalid,
} from 'shared/copy/notifications'
} from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
AlertRule,
Notification,
Kapacitor,
QueryConfig,
TimeRange,
} from 'src/types'
import {Handler} from 'src/types/kapacitor'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Props {
source: Source
rule: AlertRule
query: QueryConfig
queryConfigs: QueryConfig[]
queryConfigActions: KapacitorQueryConfigActions
ruleActions: KapacitorRuleActions
notify: (message: Notification) => void
ruleID: string
handlersFromConfig: Handler[]
router: InjectedRouter
kapacitor: Kapacitor
configLink: string
}
interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface State {
timeRange: TimeRange
}
@ErrorHandling
class KapacitorRule extends Component {
class KapacitorRule extends Component<Props, State> {
constructor(props) {
super(props)
this.state = {
@ -36,137 +78,7 @@ class KapacitorRule extends Component {
}
}
handleChooseTimeRange = ({lower}) => {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
handleCreate = pathname => {
const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
const newRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
delete newRule.queryID
createRule(kapacitor, newRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleCreated(newRule.name))
})
.catch(e => {
notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
})
}
handleEdit = pathname => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
handleSaveToConfig = configName => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError()) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
handleAddEvery = frequency => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
validationError = () => {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
handleRuleTypeDropdownChange = ({type, text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
handleRuleTypeInputChange = e => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
handleDeadmanChange = ({text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
render() {
public render() {
const {
rule,
source,
@ -180,10 +92,10 @@ class KapacitorRule extends Component {
return (
<div className="page">
<RuleHeader
source={source}
onSave={this.handleSave}
validationError={this.validationError()}
<PageHeader
titleText="Alert Rule Builder"
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
<div className="container-fluid">
@ -215,7 +127,7 @@ class KapacitorRule extends Component {
ruleActions={ruleActions}
handlersFromConfig={handlersFromConfig}
onGoToConfig={this.handleSaveToConfig}
validationError={this.validationError()}
validationError={this.validationError}
/>
<RuleMessage rule={rule} ruleActions={ruleActions} />
</div>
@ -226,27 +138,147 @@ class KapacitorRule extends Component {
</div>
)
}
}
const {arrayOf, func, shape, string} = PropTypes
private handleChooseTimeRange = ({lower}: TimeRange) => {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
KapacitorRule.propTypes = {
source: shape({}).isRequired,
rule: shape({
values: shape({}),
}).isRequired,
query: shape({}).isRequired,
queryConfigs: shape({}).isRequired,
queryConfigActions: shape({}).isRequired,
ruleActions: shape({}).isRequired,
notify: func.isRequired,
ruleID: string.isRequired,
handlersFromConfig: arrayOf(shape({})).isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
kapacitor: shape({}).isRequired,
configLink: string.isRequired,
private handleCreate = (pathname?: string) => {
const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
const newRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
delete newRule.queryID
createRule(kapacitor, newRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleCreated(newRule.name))
})
.catch(e => {
notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
})
}
private handleEdit = (pathname?: string) => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
private handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
private handleSaveToConfig = (configName: string) => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
private handleAddEvery = (frequency: string) => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
private handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
private get validationError(): string {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({lower: ''}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
private deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
private handleRuleTypeDropdownChange = ({type, text}: TypeItem) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
private handleRuleTypeInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
private handleDeadmanChange = ({text}: Item) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
private get optionsComponents(): JSX.Element {
return (
<RuleHeaderSave
onSave={this.handleSave}
validationError={this.validationError}
/>
)
}
}
const mapDispatchToProps = dispatch => ({

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemHTTP = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemHTTP: SFC<Props> = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -16,17 +21,4 @@ const LogItemHTTP = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemHTTP.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
method: string.isRequired,
username: string.isRequired,
host: string.isRequired,
duration: string.isRequired,
}),
}
export default LogItemHTTP

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemHTTPError = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemHTTPError: SFC<Props> = ({logItem}) => (
<div className="logs-table--row" key={logItem.key}>
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -16,15 +21,4 @@ const LogItemHTTPError = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemHTTPError.propTypes = {
logItem: shape({
key: string.isRequired,
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemHTTPError

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemInfluxDBDebug = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemInfluxDBDebug: SFC<Props> = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -20,15 +25,4 @@ const LogItemInfluxDBDebug = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemInfluxDBDebug.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
cluster: string.isRequired,
}),
}
export default LogItemInfluxDBDebug

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemKapacitorDebug = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemKapacitorDebug: SFC<Props> = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -16,14 +21,4 @@ const LogItemKapacitorDebug = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemKapacitorDebug.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemKapacitorDebug

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemKapacitorError = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemKapacitorError: SFC<Props> = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -16,14 +21,4 @@ const LogItemKapacitorError = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemKapacitorError.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemKapacitorError

View File

@ -1,51 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const renderKeysAndValues = (object, name) => {
if (!object) {
return <span className="logs-table--empty-cell">--</span>
}
const sortedObjKeys = Object.keys(object).sort()
return (
<div className="logs-table--column">
<h1>{`${sortedObjKeys.length} ${name}`}</h1>
<div className="logs-table--scrollbox">
{sortedObjKeys.map(objKey => (
<div key={objKey} className="logs-table--key-value">
{objKey}: <span>{object[objKey]}</span>
</div>
))}
</div>
</div>
)
}
const LogItemKapacitorPoint = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
<div className="logs-table--timestamp">{logItem.ts}</div>
</div>
<div className="logs-table--details">
<div className="logs-table--service">Kapacitor Point</div>
<div className="logs-table--columns">
{renderKeysAndValues(logItem.tag, 'Tags')}
{renderKeysAndValues(logItem.field, 'Fields')}
</div>
</div>
</div>
)
const {shape, string} = PropTypes
LogItemKapacitorPoint.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
tag: shape.isRequired,
field: shape.isRequired,
}),
}
export default LogItemKapacitorPoint

View File

@ -0,0 +1,55 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
@ErrorHandling
class LogItemKapacitorPoint extends PureComponent<Props> {
public render() {
const {logItem} = this.props
return (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
<div className="logs-table--timestamp">{logItem.ts}</div>
</div>
<div className="logs-table--details">
<div className="logs-table--service">Kapacitor Point</div>
<div className="logs-table--columns">
{this.renderKeysAndValues(logItem.tag, 'Tags')}
{this.renderKeysAndValues(logItem.field, 'Fields')}
</div>
</div>
</div>
)
}
private renderKeysAndValues = (object: any, name: string) => {
if (_.isEmpty(object)) {
return <span className="logs-table--empty-cell">--</span>
}
const sortedObjKeys = Object.keys(object).sort()
return (
<div className="logs-table--column">
<h1>{`${sortedObjKeys.length} ${name}`}</h1>
<div className="logs-table--scrollbox">
{sortedObjKeys.map(objKey => (
<div key={objKey} className="logs-table--key-value">
{objKey}: <span>{object[objKey]}</span>
</div>
))}
</div>
</div>
)
}
}
export default LogItemKapacitorPoint

View File

@ -1,7 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const LogItemSession = ({logItem}) => (
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemSession: SFC<Props> = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
@ -13,14 +18,4 @@ const LogItemSession = ({logItem}) => (
</div>
)
const {shape, string} = PropTypes
LogItemSession.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemSession

View File

@ -1,12 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import LogsTableRow from 'src/kapacitor/components/LogsTableRow'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {LogItem} from 'src/types/kapacitor'
const numLogsToRender = 200
const LogsTable = ({logs}) => (
interface Props {
logs: LogItem[]
}
const LogsTable: SFC<Props> = ({logs}) => (
<div className="logs-table">
<div className="logs-table--header">
{`${numLogsToRender} Most Recent Logs`}
@ -22,17 +27,4 @@ const LogsTable = ({logs}) => (
</div>
)
const {arrayOf, shape, string} = PropTypes
LogsTable.propTypes = {
logs: arrayOf(
shape({
key: string.isRequired,
ts: string.isRequired,
lvl: string.isRequired,
msg: string.isRequired,
})
).isRequired,
}
export default LogsTable

View File

@ -1,5 +1,4 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import LogItemSession from 'src/kapacitor/components/LogItemSession'
import LogItemHTTP from 'src/kapacitor/components/LogItemHTTP'
@ -9,7 +8,13 @@ import LogItemKapacitorError from 'src/kapacitor/components/LogItemKapacitorErro
import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug'
import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug'
const LogsTableRow = ({logItem}) => {
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogsTableRow: SFC<Props> = ({logItem}) => {
if (logItem.service === 'sessions') {
return <LogItemSession logItem={logItem} />
}
@ -51,15 +56,4 @@ const LogsTableRow = ({logItem}) => {
)
}
const {shape, string} = PropTypes
LogsTableRow.propTypes = {
logItem: shape({
key: string.isRequired,
ts: string.isRequired,
lvl: string.isRequired,
msg: string.isRequired,
}).isRequired,
}
export default LogsTableRow

View File

@ -1,28 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const LogsToggle = ({areLogsVisible, onToggleLogsVisibility}) => (
<ul className="nav nav-tablist nav-tablist-sm nav-tablist-malachite logs-toggle">
<li
className={areLogsVisible ? null : 'active'}
onClick={onToggleLogsVisibility}
>
Editor
</li>
<li
className={areLogsVisible ? 'active' : null}
onClick={onToggleLogsVisibility}
>
Editor + Logs
</li>
</ul>
)
const {bool, func} = PropTypes
LogsToggle.propTypes = {
areLogsVisible: bool,
onToggleLogsVisibility: func.isRequired,
}
export default LogsToggle

View File

@ -0,0 +1,68 @@
import React, {PureComponent} from 'react'
interface Props {
areLogsVisible: boolean
areLogsEnabled: boolean
onToggleLogsVisibility: () => void
}
class LogsToggle extends PureComponent<Props> {
public render() {
return (
<ul className="nav nav-tablist nav-tablist-sm nav-tablist-malachite">
{this.leftTab}
{this.rightTab}
</ul>
)
}
private get leftTab(): JSX.Element {
const {areLogsEnabled, areLogsVisible, onToggleLogsVisibility} = this.props
if (areLogsEnabled) {
return (
<li
className={areLogsVisible ? null : 'active'}
onClick={onToggleLogsVisibility}
>
Editor
</li>
)
}
return (
<li
className={areLogsVisible ? 'disabled' : ' disabled active'}
title="Log viewing is currently disabled"
>
Editor
</li>
)
}
private get rightTab(): JSX.Element {
const {areLogsEnabled, areLogsVisible, onToggleLogsVisibility} = this.props
if (areLogsEnabled) {
return (
<li
className={areLogsVisible ? 'active' : null}
onClick={onToggleLogsVisibility}
>
Editor + Logs
</li>
)
}
return (
<li
className={areLogsVisible ? 'disabled active' : ' disabled'}
title="Log viewing is currently disabled"
>
Editor + Logs
</li>
)
}
}
export default LogsToggle

View File

@ -1,11 +1,25 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, ChangeEvent, KeyboardEvent} from 'react'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
interface Props {
defaultName: string
onRuleRename: (id: string, name: string) => void
rule: AlertRule
}
interface State {
reset: boolean
}
@ErrorHandling
class NameSection extends Component {
constructor(props) {
class NameSection extends Component<Props, State> {
private inputRef: HTMLInputElement
constructor(props: Props) {
super(props)
this.state = {
@ -13,14 +27,22 @@ class NameSection extends Component {
}
}
handleInputBlur = reset => e => {
public handleInputBlur = (reset: boolean) => (
e: ChangeEvent<HTMLInputElement>
): void => {
const {defaultName, onRuleRename, rule} = this.props
onRuleRename(rule.id, reset ? defaultName : e.target.value)
let ruleName: string
if (reset) {
ruleName = defaultName
} else {
ruleName = e.target.value
}
onRuleRename(rule.id, ruleName)
this.setState({reset: false})
}
handleKeyDown = e => {
public handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
this.inputRef.blur()
}
@ -30,15 +52,13 @@ class NameSection extends Component {
}
}
render() {
const {rule, defaultName} = this.props
public render() {
const {defaultName} = this.props
const {reset} = this.state
return (
<div className="rule-section">
<h3 className="rule-section--heading">
{rule.id === DEFAULT_RULE_ID ? 'Name this Alert Rule' : 'Name'}
</h3>
<h3 className="rule-section--heading">{this.header}</h3>
<div className="rule-section--body">
<div className="rule-section--row rule-section--row-first rule-section--row-last">
<input
@ -55,14 +75,18 @@ class NameSection extends Component {
</div>
)
}
}
const {func, string, shape} = PropTypes
private get header() {
const {
rule: {id},
} = this.props
NameSection.propTypes = {
defaultName: string.isRequired,
onRuleRename: func.isRequired,
rule: shape({}).isRequired,
if (id === DEFAULT_RULE_ID) {
return 'Name this Alert Rule'
}
return 'Name'
}
}
export default NameSection

View File

@ -1,14 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'
import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
import React, {SFC, ChangeEvent} from 'react'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const mapToItems = (arr: string[], type: string) =>
arr.map(text => ({text, type}))
const changes = mapToItems(CHANGES, 'change')
const shifts = mapToItems(SHIFTS, 'shift')
const operators = mapToItems(RELATIVE_OPERATORS, 'operator')
const Relative = ({
interface TypeItem {
type: string
text: string
}
interface Props {
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onDropdownChange: (item: TypeItem) => void
rule: AlertRule
}
const Relative: SFC<Props> = ({
onRuleTypeInputChange,
onDropdownChange,
rule: {
@ -46,7 +59,7 @@ const Relative = ({
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck="false"
spellCheck={false}
value={value}
onChange={onRuleTypeInputChange}
required={true}
@ -56,19 +69,4 @@ const Relative = ({
</div>
)
const {shape, string, func} = PropTypes
Relative.propTypes = {
onRuleTypeInputChange: func.isRequired,
onDropdownChange: func.isRequired,
rule: shape({
values: shape({
change: string,
shift: string,
operator: string,
value: string,
}),
}),
}
export default Relative

View File

@ -26,7 +26,7 @@ interface Props {
rule: AlertRule
ruleActions: RuleActions
handlersFromConfig: Handler[]
onGoToConfig: () => void
onGoToConfig: (configName: string) => void
validationError: string
}

View File

@ -1,40 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class RuleHeader extends Component {
constructor(props) {
super(props)
}
render() {
const {source, onSave, validationError} = this.props
return (
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Alert Rule Builder</h1>
</div>
<RuleHeaderSave
source={source}
onSave={onSave}
validationError={validationError}
/>
</div>
</div>
)
}
}
const {func, shape, string} = PropTypes
RuleHeader.propTypes = {
source: shape({}).isRequired,
onSave: func.isRequired,
validationError: string.isRequired,
}
export default RuleHeader

View File

@ -1,11 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import ReactTooltip from 'react-tooltip'
import SourceIndicator from 'shared/components/SourceIndicator'
const RuleHeaderSave = ({onSave, validationError}) => (
<div className="page-header__right">
<SourceIndicator />
interface Props {
onSave: () => void
validationError?: string
}
const RuleHeaderSave: SFC<Props> = ({onSave, validationError}) => (
<>
{validationError ? (
<button
className="btn btn-success btn-sm disabled"
@ -26,14 +28,7 @@ const RuleHeaderSave = ({onSave, validationError}) => (
place="bottom"
class="influx-tooltip kapacitor-tooltip"
/>
</div>
</>
)
const {func, string} = PropTypes
RuleHeaderSave.propTypes = {
onSave: func.isRequired,
validationError: string.isRequired,
}
export default RuleHeaderSave

View File

@ -1,22 +1,24 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, ChangeEvent} from 'react'
import RuleMessageText from 'src/kapacitor/components/RuleMessageText'
import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
import {KapacitorRuleActions} from 'src/types/actions'
interface Props {
rule: AlertRule
ruleActions: KapacitorRuleActions
}
@ErrorHandling
class RuleMessage extends Component {
class RuleMessage extends Component<Props> {
constructor(props) {
super(props)
}
handleChangeMessage = e => {
const {ruleActions, rule} = this.props
ruleActions.updateMessage(rule.id, e.target.value)
}
render() {
public render() {
const {rule, ruleActions} = this.props
return (
@ -35,15 +37,11 @@ class RuleMessage extends Component {
</div>
)
}
}
const {func, shape} = PropTypes
RuleMessage.propTypes = {
rule: shape().isRequired,
ruleActions: shape({
updateMessage: func.isRequired,
}).isRequired,
private handleChangeMessage = (e: ChangeEvent<HTMLTextAreaElement>) => {
const {ruleActions, rule} = this.props
ruleActions.updateMessage(rule.id, e.target.value)
}
}
export default RuleMessage

View File

@ -1,5 +1,5 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import ReactTooltip from 'react-tooltip'
@ -8,19 +8,22 @@ import CodeData from 'src/kapacitor/components/CodeData'
import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {RuleMessage} from 'src/types/kapacitor'
import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (id: string, message: string) => void
}
// needs to be React Component for CodeData click handler to work
@ErrorHandling
class RuleMessageTemplates extends Component {
class RuleMessageTemplates extends Component<Props> {
constructor(props) {
super(props)
}
handleClickTemplate = template => () => {
const {updateMessage, rule} = this.props
updateMessage(rule.id, `${rule.message} ${template.label}`)
}
render() {
public render() {
return (
<div className="rule-section--row rule-section--row-last">
<p>Templates:</p>
@ -41,13 +44,11 @@ class RuleMessageTemplates extends Component {
</div>
)
}
}
const {func, shape} = PropTypes
RuleMessageTemplates.propTypes = {
rule: shape().isRequired,
updateMessage: func.isRequired,
private handleClickTemplate = (template: RuleMessage) => () => {
const {updateMessage, rule} = this.props
updateMessage(rule.id, `${rule.message} ${template.label}`)
}
}
export default RuleMessageTemplates

View File

@ -1,7 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC, ChangeEvent} from 'react'
const RuleMessageText = ({rule, updateMessage}) => (
import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (e: ChangeEvent<HTMLTextAreaElement>) => void
}
const RuleMessageText: SFC<Props> = ({rule, updateMessage}) => (
<div className="rule-builder--message">
<textarea
className="form-control input-sm form-malachite monotype"
@ -13,11 +19,4 @@ const RuleMessageText = ({rule, updateMessage}) => (
</div>
)
const {func, shape} = PropTypes
RuleMessageText.propTypes = {
rule: shape().isRequired,
updateMessage: func.isRequired,
}
export default RuleMessageText

View File

@ -6,10 +6,10 @@ const TICKscriptOverlay = ({tickscript, onClose}) => (
<OverlayTechnologies>
<div className="tick-script-overlay">
<div className="write-data-form--header">
<div className="page-header__left">
<h1 className="page-header__title">Generated TICKscript</h1>
<div className="page-header--left">
<h1 className="page-header--title">Generated TICKscript</h1>
</div>
<div className="page-header__right">
<div className="page-header--right">
<span className="page-header__dismiss" onClick={onClose} />
</div>
</div>

View File

@ -1,85 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
import _ from 'lodash'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
const operators = mapToItems(THRESHOLD_OPERATORS, 'operator')
const noopSubmit = e => e.preventDefault()
const getField = ({fields}) => {
const alias = _.get(fields, ['0', 'alias'], false)
if (!alias) {
return _.get(fields, ['0', 'value'], 'Select a Time-Series')
}
return alias
}
const Threshold = ({
rule: {
values: {operator, value, rangeValue},
},
query,
onDropdownChange,
onRuleTypeInputChange,
}) => (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck="false"
value={value}
onChange={onRuleTypeInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/>
{(operator === 'inside range' || operator === 'outside range') && (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck="false"
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)}
</form>
</div>
)
const {shape, string, func} = PropTypes
Threshold.propTypes = {
rule: shape({
values: shape({
operator: string,
rangeOperator: string,
value: string,
rangeValue: string,
}),
}),
onDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
query: shape({}).isRequired,
}
export default Threshold

View File

@ -0,0 +1,122 @@
import React, {Component, FormEvent, ChangeEvent} from 'react'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {getDeep} from 'src/utils/wrappers'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule, QueryConfig} from 'src/types'
interface TypeItem {
type: string
text: string
}
interface Props {
rule: AlertRule
onDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
query: QueryConfig
}
@ErrorHandling
class Threshold extends Component<Props> {
public render() {
const {
rule: {
values: {operator, value},
},
query,
onDropdownChange,
onRuleTypeInputChange,
} = this.props
return (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{this.getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={this.operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={this.noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck={false}
value={value}
onChange={onRuleTypeInputChange}
placeholder={this.firstInputPlaceholder}
/>
{this.secondInput}
</form>
</div>
)
}
private get operators() {
const type = 'operator'
return THRESHOLD_OPERATORS.map(text => {
return {text, type}
})
}
private get isSecondInputRequired() {
const {rule} = this.props
const operator = getDeep<string>(rule, 'values.operator', '')
if (operator === 'inside range' || operator === 'outside range') {
return true
}
return false
}
private get firstInputPlaceholder() {
if (this.isSecondInputRequired) {
return 'lower'
}
return null
}
private get secondInput() {
const {rule, onRuleTypeInputChange} = this.props
const rangeValue = getDeep<string>(rule, 'values.rangeValue', '')
if (this.isSecondInputRequired) {
return (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck={false}
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)
}
}
private noopSubmit = (e: FormEvent<HTMLElement>) => e.preventDefault()
private getField = ({fields}: QueryConfig): string => {
const alias = getDeep<string>(fields, '0.alias', '')
if (!alias) {
return getDeep<string>(fields, '0.value', 'Select a Time-Series')
}
return alias
}
}
export default Threshold

View File

@ -1,89 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader'
import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor'
import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls'
import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole'
import LogsTable from 'src/kapacitor/components/LogsTable'
const Tickscript = ({
onSave,
onExit,
task,
logs,
consoleMessage,
onSelectDbrps,
onChangeScript,
onChangeType,
onChangeID,
unsavedChanges,
isNewTickscript,
areLogsVisible,
areLogsEnabled,
onToggleLogsVisibility,
}) => (
<div className="page">
<TickscriptHeader
task={task}
onSave={onSave}
onExit={onExit}
unsavedChanges={unsavedChanges}
areLogsVisible={areLogsVisible}
areLogsEnabled={areLogsEnabled}
onToggleLogsVisibility={onToggleLogsVisibility}
isNewTickscript={isNewTickscript}
/>
<div className="page-contents--split">
<div
className="tickscript"
style={areLogsVisible ? {maxWidth: '50%'} : null}
>
<TickscriptEditorControls
isNewTickscript={isNewTickscript}
onSelectDbrps={onSelectDbrps}
onChangeType={onChangeType}
onChangeID={onChangeID}
task={task}
/>
<TickscriptEditor
script={task.tickscript}
onChangeScript={onChangeScript}
/>
<TickscriptEditorConsole
consoleMessage={consoleMessage}
unsavedChanges={unsavedChanges}
/>
</div>
{areLogsVisible ? <LogsTable logs={logs} /> : null}
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes
Tickscript.propTypes = {
logs: arrayOf(shape()).isRequired,
onSave: func.isRequired,
onExit: func.isRequired,
source: shape({
id: string,
}),
areLogsVisible: bool,
areLogsEnabled: bool,
onToggleLogsVisibility: func.isRequired,
task: shape({
id: string,
script: string,
dbsrps: arrayOf(shape()),
}).isRequired,
onChangeScript: func.isRequired,
onSelectDbrps: func.isRequired,
consoleMessage: string,
onChangeType: func.isRequired,
onChangeID: func.isRequired,
isNewTickscript: bool.isRequired,
unsavedChanges: bool,
}
export default Tickscript

View File

@ -0,0 +1,101 @@
import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader'
import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor'
import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls'
import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole'
import LogsTable from 'src/kapacitor/components/LogsTable'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Task} from 'src/types'
import {LogItem, DBRP} from 'src/types/kapacitor'
interface Props {
logs: LogItem[]
onSave: () => void
onExit: () => void
areLogsVisible: boolean
areLogsEnabled: boolean
onToggleLogsVisibility: () => void
task: Task
onChangeScript: (tickscript: string) => void
onSelectDbrps: (dbrps: DBRP[]) => void
consoleMessage: string
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
isNewTickscript: boolean
unsavedChanges: boolean
}
@ErrorHandling
class Tickscript extends PureComponent<Props> {
public render() {
const {
onSave,
onExit,
task,
consoleMessage,
onSelectDbrps,
onChangeScript,
onChangeType,
onChangeID,
unsavedChanges,
isNewTickscript,
areLogsVisible,
areLogsEnabled,
onToggleLogsVisibility,
} = this.props
return (
<div className="page">
<TickscriptHeader
task={task}
onSave={onSave}
onExit={onExit}
unsavedChanges={unsavedChanges}
areLogsVisible={areLogsVisible}
areLogsEnabled={areLogsEnabled}
onToggleLogsVisibility={onToggleLogsVisibility}
isNewTickscript={isNewTickscript}
/>
<div className="page-contents--split">
<div className="tickscript" style={this.style}>
<TickscriptEditorControls
isNewTickscript={isNewTickscript}
onSelectDbrps={onSelectDbrps}
onChangeType={onChangeType}
onChangeID={onChangeID}
task={task}
/>
<TickscriptEditor
script={task.tickscript}
onChangeScript={onChangeScript}
/>
<TickscriptEditorConsole
consoleMessage={consoleMessage}
unsavedChanges={unsavedChanges}
/>
</div>
{this.logsTable}
</div>
</div>
)
}
private get style() {
const {areLogsVisible} = this.props
if (areLogsVisible) {
return {maxWidth: '50%'}
}
}
private get logsTable() {
const {areLogsVisible, logs} = this.props
if (areLogsVisible) {
return <LogsTable logs={logs} />
}
}
}
export default Tickscript

View File

@ -1,20 +1,24 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Controlled as CodeMirror} from 'react-codemirror2'
import 'src/external/codemirror'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
onChangeScript: (tickscript: string) => void
script: string
}
const NOOP = () => {}
@ErrorHandling
class TickscriptEditor extends Component {
class TickscriptEditor extends Component<Props> {
constructor(props) {
super(props)
}
updateCode = (_, __, script) => {
this.props.onChangeScript(script)
}
render() {
public render() {
const {script} = this.props
const options = {
@ -31,17 +35,15 @@ class TickscriptEditor extends Component {
value={script}
onBeforeChange={this.updateCode}
options={options}
onTouchStart={NOOP}
/>
</div>
)
}
}
const {func, string} = PropTypes
TickscriptEditor.propTypes = {
onChangeScript: func,
script: string,
private updateCode = (_, __, script) => {
this.props.onChangeScript(script)
}
}
export default TickscriptEditor

View File

@ -1,7 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => {
interface Props {
consoleMessage: string
unsavedChanges: boolean
}
const TickscriptEditorConsole: SFC<Props> = ({
consoleMessage,
unsavedChanges,
}) => {
let consoleOutput = 'TICKscript is valid'
let consoleClass = 'tickscript-console--valid'
@ -20,11 +27,4 @@ const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => {
)
}
const {bool, string} = PropTypes
TickscriptEditorConsole.propTypes = {
consoleMessage: string,
unsavedChanges: bool,
}
export default TickscriptEditorConsole

View File

@ -1,48 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import TickscriptType from 'src/kapacitor/components/TickscriptType'
import MultiSelectDBDropdown from 'shared/components/MultiSelectDBDropdown'
import TickscriptID, {
TickscriptStaticID,
} from 'src/kapacitor/components/TickscriptID'
const addName = list => list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
const TickscriptEditorControls = ({
isNewTickscript,
onSelectDbrps,
onChangeType,
onChangeID,
task,
}) => (
<div className="tickscript-controls">
{isNewTickscript ? (
<TickscriptID onChangeID={onChangeID} id={task.id} />
) : (
<TickscriptStaticID id={task.name ? task.name : task.id} />
)}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={addName(task.dbrps)}
onApply={onSelectDbrps}
/>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes
TickscriptEditorControls.propTypes = {
isNewTickscript: bool.isRequired,
onSelectDbrps: func.isRequired,
onChangeType: func.isRequired,
onChangeID: func.isRequired,
task: shape({
id: string,
script: string,
dbsrps: arrayOf(shape()),
}).isRequired,
}
export default TickscriptEditorControls

View File

@ -0,0 +1,67 @@
import React, {Component, MouseEvent, ChangeEvent} from 'react'
import TickscriptType from 'src/kapacitor/components/TickscriptType'
import MultiSelectDBDropdown from 'src/shared/components/MultiSelectDBDropdown'
import TickscriptID, {
TickscriptStaticID,
} from 'src/kapacitor/components/TickscriptID'
import {Task} from 'src/types'
import {DBRP} from 'src/types/kapacitor'
interface DBRPDropdownItem extends DBRP {
name: string
}
interface Props {
isNewTickscript: boolean
onSelectDbrps: (dbrps: DBRP[]) => void
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
task: Task
}
class TickscriptEditorControls extends Component<Props> {
public render() {
const {onSelectDbrps, onChangeType, task} = this.props
return (
<div className="tickscript-controls">
{this.tickscriptID}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={this.addName(task.dbrps)}
onApply={onSelectDbrps}
/>
</div>
</div>
)
}
private get tickscriptID() {
const {isNewTickscript, onChangeID, task} = this.props
if (isNewTickscript) {
return <TickscriptID onChangeID={onChangeID} id={task.id} />
}
return <TickscriptStaticID id={this.taskID} />
}
private get taskID() {
const {
task: {name, id},
} = this.props
if (name) {
return name
}
return id
}
private addName = (list: DBRP[]): DBRPDropdownItem[] => {
const listWithName = list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
return listWithName
}
}
export default TickscriptEditorControls

View File

@ -1,6 +1,6 @@
import React, {SFC} from 'react'
import React, {Component} from 'react'
import SourceIndicator from 'src/shared/components/SourceIndicator'
import PageHeader from 'src/shared/components/PageHeader'
import LogsToggle from 'src/kapacitor/components/LogsToggle'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import TickscriptSave, {Task} from 'src/kapacitor/components/TickscriptSave'
@ -16,53 +16,70 @@ interface Props {
onToggleLogsVisibility: () => void
}
const TickscriptHeader: SFC<Props> = ({
task,
onSave,
onExit,
unsavedChanges,
areLogsEnabled,
areLogsVisible,
isNewTickscript,
onToggleLogsVisibility,
}) => (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">TICKscript Editor</h1>
</div>
{areLogsEnabled && (
class TickscriptHeader extends Component<Props> {
public render() {
return (
<PageHeader
titleText="TICKscript Editor"
fullWidth={true}
sourceIndicator={true}
optionsComponents={this.optionsComponents}
/>
)
}
private get optionsComponents(): JSX.Element {
const {
task,
onSave,
unsavedChanges,
isNewTickscript,
areLogsEnabled,
areLogsVisible,
onToggleLogsVisibility,
} = this.props
return (
<>
<LogsToggle
areLogsEnabled={areLogsEnabled}
areLogsVisible={areLogsVisible}
onToggleLogsVisibility={onToggleLogsVisibility}
/>
)}
<div className="page-header__right">
<SourceIndicator />
<TickscriptSave
task={task}
onSave={onSave}
unsavedChanges={unsavedChanges}
isNewTickscript={isNewTickscript}
/>
{unsavedChanges ? (
<ConfirmButton
text="Exit"
confirmText="Discard unsaved changes?"
confirmAction={onExit}
/>
) : (
<button
className="btn btn-default btn-sm"
title="Return to Alert Rules"
onClick={onExit}
>
Exit
</button>
)}
</div>
</div>
</div>
)
{this.saveButton}
</>
)
}
private get saveButton(): JSX.Element {
const {unsavedChanges, onExit} = this.props
if (unsavedChanges) {
return (
<ConfirmButton
text="Exit"
confirmText="Discard unsaved changes?"
confirmAction={onExit}
/>
)
}
return (
<button
className="btn btn-default btn-sm"
title="Return to Alert Rules"
onClick={onExit}
>
Exit
</button>
)
}
}
export default TickscriptHeader

View File

@ -1,14 +1,18 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, SFC, ChangeEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface TickscriptIDProps {
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
id: string
}
@ErrorHandling
class TickscriptID extends Component {
class TickscriptID extends Component<TickscriptIDProps> {
constructor(props) {
super(props)
}
render() {
public render() {
const {onChangeID, id} = this.props
return (
@ -25,19 +29,11 @@ class TickscriptID extends Component {
}
}
export const TickscriptStaticID = ({id}) => (
interface TickscriptStaticIDProps {
id: string
}
export const TickscriptStaticID: SFC<TickscriptStaticIDProps> = ({id}) => (
<h1 className="tickscript-controls--name">{id}</h1>
)
const {func, string} = PropTypes
TickscriptID.propTypes = {
onChangeID: func.isRequired,
id: string.isRequired,
}
TickscriptStaticID.propTypes = {
id: string.isRequired,
}
export default TickscriptID

View File

@ -1,30 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const STREAM = 'stream'
const BATCH = 'batch'
const TickscriptType = ({type, onChangeType}) => (
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={type === STREAM ? 'active' : ''}
onClick={onChangeType(STREAM)}
>
Stream
</li>
<li
className={type === BATCH ? 'active' : ''}
onClick={onChangeType(BATCH)}
>
Batch
</li>
</ul>
)
const {string, func} = PropTypes
TickscriptType.propTypes = {
type: string,
onChangeType: func,
}
export default TickscriptType

View File

@ -0,0 +1,40 @@
import React, {PureComponent, MouseEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
type: string
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
}
const STREAM = 'stream'
const BATCH = 'batch'
@ErrorHandling
class TickscriptType extends PureComponent<Props> {
public render() {
const {onChangeType} = this.props
return (
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={this.getClassName(STREAM)}
onClick={onChangeType(STREAM)}
>
Stream
</li>
<li className={this.getClassName(BATCH)} onClick={onChangeType(BATCH)}>
Batch
</li>
</ul>
)
}
private getClassName(type: string) {
if (type === this.props.type) {
return 'active'
}
return ''
}
}
export default TickscriptType

View File

@ -1,6 +1,4 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC, ChangeEvent} from 'react'
import _ from 'lodash'
import Deadman from 'src/kapacitor/components/Deadman'
@ -9,7 +7,16 @@ import Relative from 'src/kapacitor/components/Relative'
import DataSection from 'src/kapacitor/components/DataSection'
import RuleGraph from 'src/kapacitor/components/RuleGraph'
import {Tab, TabList, TabPanels, TabPanel, Tabs} from 'shared/components/Tabs'
import {
Tab,
TabList,
TabPanels,
TabPanel,
Tabs,
} from 'src/shared/components/Tabs'
import {AlertRule, QueryConfig, Source, TimeRange} from 'src/types'
import {KapacitorQueryConfigActions} from 'src/types/actions'
const TABS = ['Threshold', 'Relative', 'Deadman']
@ -22,13 +29,36 @@ const handleChooseTrigger = (rule, onChooseTrigger) => triggerIndex => {
const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger))
const isDeadman = rule => rule.trigger === 'deadman'
const ValuesSection = ({
interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface Props {
rule: AlertRule
onChooseTrigger: () => void
onUpdateValues: () => void
query: QueryConfig
onDeadmanChange: (item: Item) => void
onRuleTypeDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onAddEvery: (frequency: string) => void
onRemoveEvery: () => void
timeRange: TimeRange
queryConfigActions: KapacitorQueryConfigActions
source: Source
onChooseTimeRange: (timeRange: TimeRange) => void
}
const ValuesSection: SFC<Props> = ({
rule,
query,
source,
timeRange,
onAddEvery,
onRemoveEvery,
onChooseTrigger,
onDeadmanChange,
onChooseTimeRange,
@ -58,7 +88,6 @@ const ValuesSection = ({
isKapacitorRule={true}
actions={queryConfigActions}
onAddEvery={onAddEvery}
onRemoveEvery={onRemoveEvery}
isDeadman={isDeadman(rule)}
/>
</div>
@ -97,24 +126,4 @@ const ValuesSection = ({
</div>
)
const {shape, string, func} = PropTypes
ValuesSection.propTypes = {
rule: shape({
id: string,
}).isRequired,
onChooseTrigger: func.isRequired,
onUpdateValues: func.isRequired,
query: shape({}).isRequired,
onDeadmanChange: func.isRequired,
onRuleTypeDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
onAddEvery: func.isRequired,
onRemoveEvery: func.isRequired,
timeRange: shape({}).isRequired,
queryConfigActions: shape({}).isRequired,
source: shape({}).isRequired,
onChooseTimeRange: func.isRequired,
}
export default ValuesSection

View File

@ -1,26 +1,59 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux'
import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view'
import * as kapacitorQueryConfigActionCreators from 'src/kapacitor/actions/queryConfigs'
import {bindActionCreators} from 'redux'
import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index'
import {getActiveKapacitor, getKapacitorConfig} from 'src/shared/apis/index'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import KapacitorRule from 'src/kapacitor/components/KapacitorRule'
import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig'
import {notify as notifyAction} from 'shared/actions/notifications'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {
notifyKapacitorCreateFailed,
notifyCouldNotFindKapacitor,
} from 'shared/copy/notifications'
} from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
Notification,
AlertRule,
QueryConfig,
Kapacitor,
} from 'src/types'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Params {
ruleID: string
}
interface Props {
source: Source
notify: (notification: Notification) => void
rules: AlertRule[]
queryConfigs: QueryConfig[]
ruleActions: KapacitorRuleActions
queryConfigActions: KapacitorQueryConfigActions
params: Params
router: InjectedRouter
}
interface State {
handlersFromConfig: any[]
kapacitor: Kapacitor | {}
}
@ErrorHandling
class KapacitorRulePage extends Component {
constructor(props) {
class KapacitorRulePage extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
@ -29,7 +62,7 @@ class KapacitorRulePage extends Component {
}
}
async componentDidMount() {
public async componentDidMount() {
const {params, source, ruleActions, notify} = this.props
if (params.ruleID === 'new') {
@ -54,9 +87,8 @@ class KapacitorRulePage extends Component {
}
}
render() {
public render() {
const {
rules,
params,
source,
router,
@ -65,8 +97,7 @@ class KapacitorRulePage extends Component {
queryConfigActions,
} = this.props
const {handlersFromConfig, kapacitor} = this.state
const rule =
params.ruleID === 'new' ? rules[DEFAULT_RULE_ID] : rules[params.ruleID]
const rule = this.rule
const query = rule && queryConfigs[rule.queryID]
if (!query) {
@ -84,41 +115,26 @@ class KapacitorRulePage extends Component {
ruleID={params.ruleID}
router={router}
kapacitor={kapacitor}
configLink={`/sources/${source.id}/kapacitors/${kapacitor.id}/edit`}
configLink={`/sources/${source.id}/kapacitors/${this.kapacitorID}/edit`}
/>
)
}
}
const {func, shape, string} = PropTypes
private get kapacitorID(): string {
const {kapacitor} = this.state
return _.get(kapacitor, 'id')
}
KapacitorRulePage.propTypes = {
source: shape({
links: shape({
proxy: string.isRequired,
self: string.isRequired,
}),
}),
notify: func,
rules: shape({}).isRequired,
queryConfigs: shape({}).isRequired,
ruleActions: shape({
loadDefaultRule: func.isRequired,
fetchRule: func.isRequired,
chooseTrigger: func.isRequired,
addEvery: func.isRequired,
removeEvery: func.isRequired,
updateRuleValues: func.isRequired,
updateMessage: func.isRequired,
updateRuleName: func.isRequired,
}).isRequired,
queryConfigActions: shape({}).isRequired,
params: shape({
ruleID: string,
}).isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
private get rule(): AlertRule {
const {params, rules} = this.props
const ruleID = _.get(params, 'ruleID')
if (ruleID === 'new') {
return rules[DEFAULT_RULE_ID]
}
return rules[params.ruleID]
}
}
const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({

View File

@ -7,9 +7,9 @@ import {getActiveKapacitor} from 'src/shared/apis'
import * as kapacitorActionCreators from '../actions/view'
import KapacitorRules from 'src/kapacitor/components/KapacitorRules'
import SourceIndicator from 'src/shared/components/SourceIndicator'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import PageHeader from 'src/shared/components/PageHeader'
import {Source, Kapacitor, AlertRule} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -89,20 +89,11 @@ interface PageContentsProps {
const PageContents: SFC<PageContentsProps> = ({children}) => (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Manage Tasks</h1>
</div>
<div className="page-header__right">
<QuestionMarkTooltip
tipID="manage-tasks--tooltip"
tipContent="<b>Alert Rules</b> generate a TICKscript for<br/>you using our Builder UI.<br/><br/>Not all TICKscripts can be edited<br/>using the Builder."
/>
<SourceIndicator />
</div>
</div>
</div>
<PageHeader
titleText="Manage Tasks"
optionsComponents={renderHeaderOptions()}
sourceIndicator={true}
/>
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
<div className="container-fluid">
<div className="row">
@ -113,6 +104,15 @@ const PageContents: SFC<PageContentsProps> = ({children}) => (
</div>
)
const renderHeaderOptions = (): JSX.Element => {
return (
<QuestionMarkTooltip
tipID="manage-tasks--tooltip"
tipContent="<b>Alert Rules</b> generate a TICKscript for<br/>you using our Builder UI.<br/><br/>Not all TICKscripts can be edited<br/>using the Builder."
/>
)
}
const mapStateToProps = state => {
return {
rules: Object.values(state.rules),

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'
import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import uuid from 'uuid'
@ -18,7 +18,7 @@ import {
Notification,
NotificationFunc,
} from 'src/types'
import {LogItem, DBRP} from 'src/types/kapacitor'
import {
notifyTickscriptLoggingUnavailable,
notifyTickscriptLoggingError,
@ -76,7 +76,7 @@ interface State {
task: Task
consoleMessage: string
isEditingID: boolean
logs: object[]
logs: LogItem[]
areLogsVisible: boolean
areLogsEnabled: boolean
failStr: string
@ -247,22 +247,22 @@ export class TickscriptPage extends PureComponent<Props, State> {
return router.push(`/sources/${sourceID}/alert-rules`)
}
private handleChangeScript = tickscript => {
private handleChangeScript = (tickscript: string) => {
this.setState({
task: {...this.state.task, tickscript},
unsavedChanges: true,
})
}
private handleSelectDbrps = dbrps => {
private handleSelectDbrps = (dbrps: DBRP[]) => {
this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true})
}
private handleChangeType = type => () => {
private handleChangeType = (type: string) => () => {
this.setState({task: {...this.state.task, type}, unsavedChanges: true})
}
private handleChangeID = e => {
private handleChangeID = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({
task: {...this.state.task, id: e.target.value},
unsavedChanges: true,

View File

@ -3,6 +3,8 @@ import React, {PureComponent} from 'react'
import {Source, Namespace} from 'src/types'
import classnames from 'classnames'
import Dropdown from 'src/shared/components/Dropdown'
import PageHeader from 'src/shared/components/PageHeader'
import PageHeaderTitle from 'src/shared/components/PageHeaderTitle'
import TimeRangeDropdown from 'src/logs/components/TimeRangeDropdown'
import {TimeRange} from 'src/types'
@ -27,37 +29,47 @@ interface Props {
class LogViewerHeader extends PureComponent<Props> {
public render(): JSX.Element {
const {timeRange} = this.props
return (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
{this.status}
<h1 className="page-header__title logs-viewer-header-title">
Log Viewer
</h1>
</div>
<div className="page-header__right">
<Dropdown
className="dropdown-300"
items={this.sourceDropDownItems}
selected={this.selectedSource}
onChoose={this.handleChooseSource}
/>
<Dropdown
className="dropdown-180"
iconName="disks"
items={this.namespaceDropDownItems}
selected={this.selectedNamespace}
onChoose={this.handleChooseNamespace}
/>
<TimeRangeDropdown
onChooseTimeRange={this.handleChooseTimeRange}
selected={timeRange}
/>
</div>
</div>
</div>
<PageHeader
titleComponents={this.renderHeaderTitle}
fullWidth={true}
optionsComponents={this.optionsComponents}
/>
)
}
private get renderHeaderTitle(): JSX.Element {
return (
<>
{this.status}
<PageHeaderTitle title="Logs Viewer" />
</>
)
}
private get optionsComponents(): JSX.Element {
const {timeRange} = this.props
return (
<>
<Dropdown
className="dropdown-300"
items={this.sourceDropDownItems}
selected={this.selectedSource}
onChoose={this.handleChooseSource}
/>
<Dropdown
className="dropdown-180"
iconName="disks"
items={this.namespaceDropDownItems}
selected={this.selectedNamespace}
onChoose={this.handleChooseNamespace}
/>
<TimeRangeDropdown
onChooseTimeRange={this.handleChooseTimeRange}
selected={timeRange}
/>
</>
)
}

View File

@ -3,7 +3,7 @@ import {Notification} from 'src/types'
export type Action = ActionPublishNotification | ActionDismissNotification
// Publish notification
export type PubishNotification = (n: Notification) => ActionPublishNotification
export type PublishNotification = (n: Notification) => ActionPublishNotification
export interface ActionPublishNotification {
type: 'PUBLISH_NOTIFICATION'
payload: {

View File

@ -1,20 +1,47 @@
import {proxy} from 'src/utils/queryUrlGenerator'
import {noop} from 'src/shared/actions/app'
import _ from 'lodash'
import {errorThrown} from 'src/shared/actions/errors'
import {TimeSeriesResponse, TimeSeriesSeries} from 'src/types/series'
import {Status} from 'src/types'
import {getDeep} from 'src/utils/wrappers'
export const handleLoading = (query, editQueryStatus) => {
interface Query {
host: string | string[]
text: string
id: string
database?: string
db?: string
rp?: string
}
interface Payload {
source: string
query: Query
tempVars: any[]
db?: string
rp?: string
resolution?: number
}
type EditQueryStatusFunction = (queryID: string, status: Status) => void
const handleLoading = (
query: Query,
editQueryStatus: EditQueryStatusFunction
): void =>
editQueryStatus(query.id, {
loading: true,
})
}
// {results: [{}]}
export const handleSuccess = (data, query, editQueryStatus) => {
const handleSuccess = (
data: TimeSeriesResponse,
query: Query,
editQueryStatus: EditQueryStatusFunction
): TimeSeriesResponse => {
const {results} = data
const error = _.get(results, ['0', 'error'], false)
const series = _.get(results, ['0', 'series'], false)
const error = getDeep<string>(results, '0.error', null)
const series = getDeep<TimeSeriesSeries>(results, '0.series', null)
// 200 from server and no results = warn
if (!series && !error) {
editQueryStatus(query.id, {
@ -38,12 +65,14 @@ export const handleSuccess = (data, query, editQueryStatus) => {
return data
}
export const handleError = (error, query, editQueryStatus) => {
const message = _.get(
error,
['data', 'message'],
error.message || 'Could not retrieve data'
)
const handleError = (
error,
query: Query,
editQueryStatus: EditQueryStatusFunction
): void => {
const message =
getDeep<string>(error, 'data.message', '') ||
getDeep<string>(error, 'message', 'Could not retrieve data')
// 400 from chrono server = fail
editQueryStatus(query.id, {
@ -51,28 +80,10 @@ export const handleError = (error, query, editQueryStatus) => {
})
}
interface Query {
host: string | string[]
text: string
id: string
database?: string
db?: string
rp?: string
}
interface Payload {
source: string
query: Query
tempVars: any[]
db?: string
rp?: string
resolution?: number
}
export const fetchTimeSeriesAsync = async (
{source, db, rp, query, tempVars, resolution}: Payload,
editQueryStatus = noop
) => {
editQueryStatus: EditQueryStatusFunction = noop
): Promise<TimeSeriesResponse> => {
handleLoading(query, editQueryStatus)
try {
const {data} = await proxy({

View File

@ -45,6 +45,10 @@ class AnnotationInput extends Component<Props, State> {
</div>
)
}
public handleClickOutside = () => {
this.props.onConfirmUpdate()
this.setState({isEditing: false})
}
private handleInputClick = () => {
this.setState({isEditing: true})

View File

@ -1,7 +1,10 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import {ErrorHandling} from 'src/shared/decorators/errors'
type Position = 'top' | 'bottom' | 'left' | 'right'
interface Props {
text?: string
confirmText?: string
@ -12,6 +15,7 @@ interface Props {
icon?: string
disabled?: boolean
customClass?: string
position?: Position
}
interface State {
@ -47,10 +51,11 @@ class ConfirmButton extends PureComponent<Props, State> {
className={this.className}
onClick={this.handleButtonClick}
ref={r => (this.buttonDiv = r)}
title={confirmText}
>
{icon && <span className={`icon ${icon}`} />}
{text && text}
<div className={`confirm-button--tooltip ${this.calculatedPosition}`}>
<div className={this.tooltipClassName}>
<div
className="confirm-button--confirmation"
onClick={this.handleConfirmClick}
@ -64,18 +69,6 @@ class ConfirmButton extends PureComponent<Props, State> {
)
}
private get className() {
const {type, size, square, disabled, customClass} = this.props
const {expanded} = this.state
const customClassString = customClass ? ` ${customClass}` : ''
const squareString = square ? ' btn-square' : ''
const expandedString = expanded ? ' active' : ''
const disabledString = disabled ? ' disabled' : ''
return `confirm-button btn ${type} ${size}${customClassString}${squareString}${expandedString}${disabledString}`
}
private handleButtonClick = () => {
if (this.props.disabled) {
return
@ -92,9 +85,27 @@ class ConfirmButton extends PureComponent<Props, State> {
this.setState({expanded: false})
}
private get calculatedPosition() {
private get className(): string {
const {type, size, square, disabled, customClass} = this.props
const {expanded} = this.state
return classnames(`confirm-button btn ${type} ${size}`, {
[customClass]: customClass,
'btn-square': square,
active: expanded,
disabled,
})
}
private get tooltipClassName(): string {
const {position} = this.props
if (position) {
return `confirm-button--tooltip ${position}`
}
if (!this.buttonDiv || !this.tooltipDiv) {
return ''
return 'confirm-button--tooltip bottom'
}
const windowWidth = window.innerWidth
@ -104,10 +115,10 @@ class ConfirmButton extends PureComponent<Props, State> {
const rightGap = windowWidth - buttonRect.right
if (tooltipRect.width / 2 > rightGap) {
return 'left'
return 'confirm-button--tooltip left'
}
return 'bottom'
return 'confirm-button--tooltip bottom'
}
}

View File

@ -178,8 +178,10 @@ class Dygraph extends Component<Props, State> {
}
public componentWillUnmount() {
this.dygraph.destroy()
delete this.dygraph
if (this.dygraph) {
this.dygraph.destroy()
delete this.dygraph
}
}
public shouldComponentUpdate(nextProps: Props, nextState: State) {

View File

@ -112,6 +112,13 @@ class NewAnnotation extends Component<Props, State> {
)
}
public handleClickOutside = () => {
const {onDismissAddingAnnotation, isTempHovering} = this.props
if (!isTempHovering) {
onDismissAddingAnnotation()
}
}
private clampWithinGraphTimerange = (timestamp: number): number => {
const [xRangeStart] = this.props.dygraph.xAxisRange()
return Math.max(xRangeStart, timestamp)

View File

@ -0,0 +1,80 @@
import React, {Component, ReactElement} from 'react'
import classnames from 'classnames'
import Title from 'src/shared/components/PageHeaderTitle'
import SourceIndicator from 'src/shared/components/SourceIndicator'
interface Props {
titleText?: string
titleComponents?: ReactElement<any>
optionsComponents?: ReactElement<any>
fullWidth?: boolean
sourceIndicator?: boolean
inPresentationMode?: boolean
}
class PageHeader extends Component<Props> {
public render() {
const {inPresentationMode} = this.props
if (inPresentationMode) {
return null
}
return (
<div className={this.className}>
<div className="page-header--container">
<div className="page-header--left">{this.renderLeft}</div>
<div className="page-header--right">
{this.sourceIndicator}
{this.renderRight}
</div>
</div>
</div>
)
}
private get sourceIndicator(): JSX.Element {
const {sourceIndicator} = this.props
if (!sourceIndicator) {
return
}
return <SourceIndicator />
}
private get renderLeft(): JSX.Element {
const {titleText, titleComponents} = this.props
if (!titleText && !titleComponents) {
console.error(
'PageHeader requires either titleText or titleComponents prop'
)
}
if (!titleComponents) {
return <Title title={titleText} />
}
return titleComponents
}
private get renderRight(): JSX.Element {
const {optionsComponents} = this.props
if (!optionsComponents) {
return
}
return optionsComponents
}
private get className(): string {
const {fullWidth} = this.props
return classnames('page-header', {'full-width': fullWidth})
}
}
export default PageHeader

View File

@ -0,0 +1,11 @@
import React, {SFC} from 'react'
interface Props {
title: string
}
const PageTitle: SFC<Props> = ({title}) => (
<h1 className="page-header--title">{title}</h1>
)
export default PageTitle

View File

@ -1,47 +1,46 @@
import React, {SFC} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import uuid from 'uuid'
import ReactTooltip from 'react-tooltip'
import {SourceContext} from 'src/CheckSources'
import {Source} from 'src/types'
interface Props {
sourceOverride?: Source
}
const SourceIndicator: SFC<Props> = (
{sourceOverride},
{source: {name, url}}
) => {
const getTooltipText = (source: Source, sourceOverride: Source): string => {
const {name, url} = source
const sourceName: string = _.get(sourceOverride, 'name', name)
const sourceUrl: string = _.get(sourceOverride, 'url', url)
const sourceNameTooltip: string = `<h1>Connected to Source:</h1><p><code>${sourceName} @ ${sourceUrl}</code></p>`
const uuidTooltip: string = uuid.v4()
return sourceName ? (
<div
className="source-indicator"
data-for={uuidTooltip}
data-tip={sourceNameTooltip}
>
<span className="icon disks" />
<ReactTooltip
id={uuidTooltip}
effect="solid"
html={true}
place="left"
class="influx-tooltip"
/>
</div>
) : null
return `<h1>Connected to Source:</h1><p><code>${sourceName} @ ${sourceUrl}</code></p>`
}
const {shape} = PropTypes
const SourceIndicator: SFC<Props> = ({sourceOverride}) => {
const uuidTooltip: string = uuid.v4()
SourceIndicator.contextTypes = {
source: shape({}),
return (
<SourceContext.Consumer>
{(source: Source) => (
<div
className="source-indicator"
data-for={uuidTooltip}
data-tip={getTooltipText(source, sourceOverride)}
>
<span className="icon disks" />
<ReactTooltip
id={uuidTooltip}
effect="solid"
html={true}
place="left"
class="influx-tooltip"
/>
</div>
)}
</SourceContext.Consumer>
)
}
export default SourceIndicator

View File

@ -48,7 +48,7 @@ interface TabListProps {
activeIndex?: number
onActivate?: (index: number) => void
isKapacitorTabs?: string
customClass: string
customClass?: string
}
export const TabList: SFC<TabListProps> = ({
@ -97,7 +97,7 @@ TabList.defaultProps = {
interface TabPanelsProps {
children: JSX.Element[] | JSX.Element
activeIndex?: number
customClass: string
customClass?: string
}
export const TabPanels: SFC<TabPanelsProps> = ({
@ -120,7 +120,7 @@ export const TabPanel: SFC<TabPanelProps> = ({children}) => (
interface TabsProps {
children: JSX.Element[] | JSX.Element
onSelect?: (activeIndex: number) => void
tabContentsClass: string
tabContentsClass?: string
tabsClass?: string
initialIndex?: number
}

View File

@ -1,22 +1,29 @@
// All copy for notifications should be stored here for easy editing
// and ensuring stylistic consistency
import {Notification} from 'src/types'
import {TemplateUpdate} from 'src/types/tempVars'
type NotificationExcludingMessage = Pick<
Notification,
Exclude<keyof Notification, 'message'>
>
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index'
import {MAX_RESPONSE_BYTES} from 'src/flux/constants'
const defaultErrorNotification = {
const defaultErrorNotification: NotificationExcludingMessage = {
type: 'error',
icon: 'alert-triangle',
duration: TEN_SECONDS,
}
const defaultSuccessNotification = {
const defaultSuccessNotification: NotificationExcludingMessage = {
type: 'success',
icon: 'checkmark',
duration: FIVE_SECONDS,
}
const defaultDeletionNotification = {
const defaultDeletionNotification: NotificationExcludingMessage = {
type: 'primary',
icon: 'trash',
duration: FIVE_SECONDS,
@ -24,211 +31,240 @@ const defaultDeletionNotification = {
// Misc Notifications
// ----------------------------------------------------------------------------
export const notifyGenericFail = () => 'Could not communicate with server.'
export const notifyGenericFail = (): string =>
'Could not communicate with server.'
export const notifyNewVersion = version => ({
export const notifyNewVersion = (version: string): Notification => ({
type: 'info',
icon: 'cubo-uniform',
duration: INFINITE,
message: `Welcome to the latest Chronograf${version}. Local settings cleared.`,
})
export const notifyLoadLocalSettingsFailed = error => ({
export const notifyLoadLocalSettingsFailed = (error: string): Notification => ({
...defaultErrorNotification,
message: `Loading local settings failed: ${error}`,
})
export const notifyErrorWithAltText = (type, message) => ({
export const notifyErrorWithAltText = (
type: string,
message: string
): Notification => ({
type,
icon: 'triangle',
duration: TEN_SECONDS,
message,
})
export const notifyPresentationMode = () => ({
export const notifyPresentationMode = (): Notification => ({
type: 'primary',
icon: 'expand-b',
duration: 7500,
message: 'Press ESC to exit Presentation Mode.',
})
export const notifyDataWritten = () => ({
export const notifyDataWritten = (): Notification => ({
...defaultSuccessNotification,
message: 'Data was written successfully.',
})
export const notifyDataWriteFailed = errorMessage => ({
export const notifyDataWriteFailed = (errorMessage: string): Notification => ({
...defaultErrorNotification,
message: `Data write failed: ${errorMessage}`,
})
export const notifySessionTimedOut = () => ({
export const notifySessionTimedOut = (): Notification => ({
type: 'primary',
icon: 'triangle',
duration: INFINITE,
message: 'Your session has timed out. Log in again to continue.',
})
export const notifyServerError = {
export const notifyServerError: Notification = {
...defaultErrorNotification,
message: 'Internal Server Error. Check API Logs.',
}
export const notifyCouldNotRetrieveKapacitors = sourceID => ({
export const notifyCouldNotRetrieveKapacitors = (
sourceID: string
): Notification => ({
...defaultErrorNotification,
message: `Internal Server Error. Could not retrieve Kapacitor Connections for source ${sourceID}.`,
})
export const notifyCouldNotRetrieveKapacitorServices = kapacitor => ({
export const notifyCouldNotRetrieveKapacitorServices = (
kapacitor: string
): Notification => ({
...defaultErrorNotification,
message: `Interanl Server Error. Could not retrieve services for Kapacitor ${kapacitor}`,
message: `Internal Server Error. Could not retrieve services for Kapacitor ${kapacitor}`,
})
export const notifyCouldNotDeleteKapacitor = () => ({
export const notifyCouldNotDeleteKapacitor = (): Notification => ({
...defaultErrorNotification,
message: 'Internal Server Error. Could not delete Kapacitor Connection.',
})
export const notifyCSVDownloadFailed = () => ({
export const notifyCSVDownloadFailed = (): Notification => ({
...defaultErrorNotification,
message: 'Unable to download .CSV file',
})
// Hosts Page Notifications
// ----------------------------------------------------------------------------
export const notifyUnableToGetHosts = () => ({
export const notifyUnableToGetHosts = (): Notification => ({
...defaultErrorNotification,
message: 'Unable to get Hosts.',
})
export const notifyUnableToGetApps = () => ({
export const notifyUnableToGetApps = (): Notification => ({
...defaultErrorNotification,
message: 'Unable to get Apps for Hosts.',
})
// InfluxDB Sources Notifications
// ----------------------------------------------------------------------------
export const notifySourceCreationSucceeded = sourceName => ({
export const notifySourceCreationSucceeded = (
sourceName: string
): Notification => ({
...defaultSuccessNotification,
icon: 'server2',
message: `Connected to InfluxDB ${sourceName} successfully.`,
})
export const notifySourceCreationFailed = (sourceName, errorMessage) => ({
export const notifySourceCreationFailed = (
sourceName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `Unable to connect to InfluxDB ${sourceName}: ${errorMessage}`,
})
export const notifySourceUdpated = sourceName => ({
export const notifySourceUdpated = (sourceName: string): Notification => ({
...defaultSuccessNotification,
icon: 'server2',
message: `Updated InfluxDB ${sourceName} Connection successfully.`,
})
export const notifySourceUdpateFailed = (sourceName, errorMessage) => ({
export const notifySourceUdpateFailed = (
sourceName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`,
})
export const notifySourceDeleted = (sourceName: string) => ({
export const notifySourceDeleted = (sourceName: string): Notification => ({
...defaultSuccessNotification,
icon: 'server2',
message: `${sourceName} deleted successfully.`,
})
export const notifySourceDeleteFailed = sourceName => ({
export const notifySourceDeleteFailed = (sourceName: string): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `There was a problem deleting ${sourceName}.`,
})
export const notifySourceNoLongerAvailable = sourceName =>
`Source ${sourceName} is no longer available. Please ensure InfluxDB is running.`
export const notifyNoSourcesAvailable = sourceName =>
`Unable to connect to source ${sourceName}. No other sources available.`
export const notifyUnableToRetrieveSources = () => 'Unable to retrieve sources.'
export const notifyUnableToConnectSource = sourceName =>
`Unable to connect to source ${sourceName}.`
export const notifyErrorConnectingToSource = errorMessage => ({
export const notifySourceNoLongerAvailable = (
sourceName: string
): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `Source ${sourceName} is no longer available. Please ensure InfluxDB is running.`,
})
export const notifyErrorConnectingToSource = (
errorMessage: string
): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `Unable to connect to InfluxDB source: ${errorMessage}`,
})
// Multitenancy User Notifications
// ----------------------------------------------------------------------------
export const notifyUserRemovedFromAllOrgs = () => ({
export const notifyUserRemovedFromAllOrgs = (): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message:
'You have been removed from all organizations. Please contact your administrator.',
})
export const notifyUserRemovedFromCurrentOrg = () => ({
export const notifyUserRemovedFromCurrentOrg = (): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message: 'You were removed from your current organization.',
})
export const notifyOrgHasNoSources = () => ({
export const notifyOrgHasNoSources = (): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message: 'Organization has no sources configured.',
})
export const notifyUserSwitchedOrgs = (orgName, roleName) => ({
export const notifyUserSwitchedOrgs = (
orgName: string,
roleName: string
): Notification => ({
...defaultSuccessNotification,
type: 'primary',
message: `Now logged in to '${orgName}' as '${roleName}'.`,
})
export const notifyOrgIsPrivate = () => ({
export const notifyOrgIsPrivate = (): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message:
'This organization is private. To gain access, you must be explicitly added by an administrator.',
})
export const notifyCurrentOrgDeleted = () => ({
export const notifyCurrentOrgDeleted = (): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message: 'Your current organization was deleted.',
})
export const notifyJSONFeedFailed = url => ({
export const notifyJSONFeedFailed = (url: string): Notification => ({
...defaultErrorNotification,
message: `Failed to fetch JSON Feed for News Feed from '${url}'`,
})
// Chronograf Admin Notifications
// ----------------------------------------------------------------------------
export const notifyMappingDeleted = (id, scheme) => ({
export const notifyMappingDeleted = (
id: string,
scheme: string
): Notification => ({
...defaultSuccessNotification,
message: `Mapping ${id}/${scheme} deleted successfully.`,
})
export const notifyChronografUserAddedToOrg = (user, organization) =>
`${user} has been added to ${organization} successfully.`
export const notifyChronografUserAddedToOrg = (
user: string,
organization: string
): string => `${user} has been added to ${organization} successfully.`
export const notifyChronografUserRemovedFromOrg = (user, organization) =>
`${user} has been removed from ${organization} successfully.`
export const notifyChronografUserRemovedFromOrg = (
user: string,
organization: string
): string => `${user} has been removed from ${organization} successfully.`
export const notifyChronografUserUpdated = message => ({
export const notifyChronografUserUpdated = (message: string): Notification => ({
...defaultSuccessNotification,
message,
})
export const notifyChronografOrgDeleted = orgName => ({
export const notifyChronografOrgDeleted = (orgName: string): Notification => ({
...defaultSuccessNotification,
message: `Organization ${orgName} deleted successfully.`,
})
export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({
export const notifyChronografUserDeleted = (
user: string,
isAbsoluteDelete: boolean
): Notification => ({
...defaultSuccessNotification,
message: `${user} has been removed from ${
isAbsoluteDelete
@ -237,7 +273,7 @@ export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({
}`,
})
export const notifyChronografUserMissingNameAndProvider = () => ({
export const notifyChronografUserMissingNameAndProvider = (): Notification => ({
...defaultErrorNotification,
type: 'warning',
message: 'User must have a Name and Provider.',
@ -245,220 +281,238 @@ export const notifyChronografUserMissingNameAndProvider = () => ({
// InfluxDB Admin Notifications
// ----------------------------------------------------------------------------
export const notifyDBUserCreated = () => ({
export const notifyDBUserCreated = (): Notification => ({
...defaultSuccessNotification,
message: 'User created successfully.',
})
export const notifyDBUserCreationFailed = errorMessage =>
export const notifyDBUserCreationFailed = (errorMessage: string): string =>
`Failed to create User: ${errorMessage}`
export const notifyDBUserDeleted = userName => ({
export const notifyDBUserDeleted = (userName: string): Notification => ({
...defaultSuccessNotification,
message: `User "${userName}" deleted successfully.`,
})
export const notifyDBUserDeleteFailed = errorMessage =>
export const notifyDBUserDeleteFailed = (errorMessage: string): string =>
`Failed to delete User: ${errorMessage}`
export const notifyDBUserPermissionsUpdated = () => ({
export const notifyDBUserPermissionsUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'User Permissions updated successfully.',
})
export const notifyDBUserPermissionsUpdateFailed = errorMessage =>
`Failed to update User Permissions: ${errorMessage}`
export const notifyDBUserPermissionsUpdateFailed = (
errorMessage: string
): string => `Failed to update User Permissions: ${errorMessage}`
export const notifyDBUserRolesUpdated = () => ({
export const notifyDBUserRolesUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'User Roles updated successfully.',
})
export const notifyDBUserRolesUpdateFailed = errorMessage =>
export const notifyDBUserRolesUpdateFailed = (errorMessage: string): string =>
`Failed to update User Roles: ${errorMessage}`
export const notifyDBUserPasswordUpdated = () => ({
export const notifyDBUserPasswordUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'User Password updated successfully.',
})
export const notifyDBUserPasswordUpdateFailed = errorMessage =>
`Failed to update User Password: ${errorMessage}`
export const notifyDBUserPasswordUpdateFailed = (
errorMessage: string
): string => `Failed to update User Password: ${errorMessage}`
export const notifyDatabaseCreated = () => ({
export const notifyDatabaseCreated = (): Notification => ({
...defaultSuccessNotification,
message: 'Database created successfully.',
})
export const notifyDBCreationFailed = errorMessage =>
export const notifyDBCreationFailed = (errorMessage: string): string =>
`Failed to create Database: ${errorMessage}`
export const notifyDBDeleted = databaseName => ({
export const notifyDBDeleted = (databaseName: string): Notification => ({
...defaultSuccessNotification,
message: `Database "${databaseName}" deleted successfully.`,
})
export const notifyDBDeleteFailed = errorMessage =>
export const notifyDBDeleteFailed = (errorMessage: string): string =>
`Failed to delete Database: ${errorMessage}`
export const notifyRoleCreated = () => ({
export const notifyRoleCreated = (): Notification => ({
...defaultSuccessNotification,
message: 'Role created successfully.',
})
export const notifyRoleCreationFailed = errorMessage =>
export const notifyRoleCreationFailed = (errorMessage: string): string =>
`Failed to create Role: ${errorMessage}`
export const notifyRoleDeleted = roleName => ({
export const notifyRoleDeleted = (roleName: string): Notification => ({
...defaultSuccessNotification,
message: `Role "${roleName}" deleted successfully.`,
})
export const notifyRoleDeleteFailed = errorMessage =>
export const notifyRoleDeleteFailed = (errorMessage: string): string =>
`Failed to delete Role: ${errorMessage}`
export const notifyRoleUsersUpdated = () => ({
export const notifyRoleUsersUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'Role Users updated successfully.',
})
export const notifyRoleUsersUpdateFailed = errorMessage =>
export const notifyRoleUsersUpdateFailed = (errorMessage: string): string =>
`Failed to update Role Users: ${errorMessage}`
export const notifyRolePermissionsUpdated = () => ({
export const notifyRolePermissionsUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'Role Permissions updated successfully.',
})
export const notifyRolePermissionsUpdateFailed = errorMessage =>
`Failed to update Role Permissions: ${errorMessage}`
export const notifyRolePermissionsUpdateFailed = (
errorMessage: string
): string => `Failed to update Role Permissions: ${errorMessage}`
export const notifyRetentionPolicyCreated = () => ({
export const notifyRetentionPolicyCreated = (): Notification => ({
...defaultSuccessNotification,
message: 'Retention Policy created successfully.',
})
export const notifyRetentionPolicyCreationError = () => ({
export const notifyRetentionPolicyCreationError = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to create Retention Policy. Please check name and duration.',
})
export const notifyRetentionPolicyCreationFailed = errorMessage =>
`Failed to create Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyCreationFailed = (
errorMessage: string
): string => `Failed to create Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyDeleted = rpName => ({
export const notifyRetentionPolicyDeleted = (rpName: string): Notification => ({
...defaultSuccessNotification,
message: `Retention Policy "${rpName}" deleted successfully.`,
})
export const notifyRetentionPolicyDeleteFailed = errorMessage =>
`Failed to delete Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyDeleteFailed = (
errorMessage: string
): string => `Failed to delete Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyUpdated = () => ({
export const notifyRetentionPolicyUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'Retention Policy updated successfully.',
})
export const notifyRetentionPolicyUpdateFailed = errorMessage =>
`Failed to update Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyUpdateFailed = (
errorMessage: string
): string => `Failed to update Retention Policy: ${errorMessage}`
export const notifyQueriesError = errorMessage => ({
export const notifyQueriesError = (errorMessage: string): Notification => ({
...defaultErrorNotification,
errorMessage,
message: errorMessage,
})
export const notifyRetentionPolicyCantHaveEmptyFields = () => ({
export const notifyRetentionPolicyCantHaveEmptyFields = (): Notification => ({
...defaultErrorNotification,
message: 'Fields cannot be empty.',
})
export const notifyDatabaseDeleteConfirmationRequired = databaseName => ({
export const notifyDatabaseDeleteConfirmationRequired = (
databaseName: string
): Notification => ({
...defaultErrorNotification,
message: `Type "DELETE ${databaseName}" to confirm. This action cannot be undone.`,
})
export const notifyDBUserNamePasswordInvalid = () => ({
export const notifyDBUserNamePasswordInvalid = (): Notification => ({
...defaultErrorNotification,
message: 'Username and/or Password too short.',
})
export const notifyRoleNameInvalid = () => ({
export const notifyRoleNameInvalid = (): Notification => ({
...defaultErrorNotification,
message: 'Role name is too short.',
})
export const notifyDatabaseNameInvalid = () => ({
export const notifyDatabaseNameInvalid = (): Notification => ({
...defaultErrorNotification,
message: 'Database name cannot be blank.',
})
export const notifyDatabaseNameAlreadyExists = () => ({
export const notifyDatabaseNameAlreadyExists = (): Notification => ({
...defaultErrorNotification,
message: 'A Database by this name already exists.',
})
// Dashboard Notifications
// ----------------------------------------------------------------------------
export const notifyTempVarAlreadyExists = tempVarName => ({
export const notifyTempVarAlreadyExists = (
tempVarName: string
): Notification => ({
...defaultErrorNotification,
icon: 'cube',
message: `Variable '${tempVarName}' already exists. Please enter a new value.`,
})
export const notifyDashboardNotFound = dashboardID => ({
export const notifyDashboardNotFound = (dashboardID: number): Notification => ({
...defaultErrorNotification,
icon: 'dash-h',
message: `Dashboard ${dashboardID} could not be found`,
})
export const notifyDashboardDeleted = name => ({
export const notifyDashboardDeleted = (name: string): Notification => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `Dashboard ${name} deleted successfully.`,
})
export const notifyDashboardExported = name => ({
export const notifyDashboardExported = (name: string): Notification => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `Dashboard ${name} exported successfully.`,
})
export const notifyDashboardExportFailed = (name, errorMessage) => ({
export const notifyDashboardExportFailed = (
name: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message: `Failed to export Dashboard ${name}: ${errorMessage}.`,
})
export const notifyDashboardImported = name => ({
export const notifyDashboardImported = (name: string): Notification => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `Dashboard ${name} imported successfully.`,
})
export const notifyDashboardImportFailed = (fileName, errorMessage) => ({
export const notifyDashboardImportFailed = (
fileName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
duration: INFINITE,
message: `Failed to import Dashboard from file ${fileName}: ${errorMessage}.`,
})
export const notifyDashboardDeleteFailed = (name, errorMessage) =>
`Failed to delete Dashboard ${name}: ${errorMessage}.`
export const notifyDashboardDeleteFailed = (
name: string,
errorMessage: string
): string => `Failed to delete Dashboard ${name}: ${errorMessage}.`
export const notifyCellAdded = name => ({
export const notifyCellAdded = (name: string): Notification => ({
...defaultSuccessNotification,
icon: 'dash-h',
duration: 1900,
message: `Added "${name}" to dashboard.`,
})
export const notifyCellDeleted = name => ({
export const notifyCellDeleted = (name: string): Notification => ({
...defaultDeletionNotification,
icon: 'dash-h',
duration: 1900,
message: `Deleted "${name}" from dashboard.`,
})
export const notifyBuilderDisabled = () => ({
export const notifyBuilderDisabled = (): Notification => ({
type: 'info',
icon: 'graphline',
duration: 7500,
@ -467,249 +521,276 @@ export const notifyBuilderDisabled = () => ({
// Template Variables & URL Queries
// ----------------------------------------------------------------------------
export const notifyInvalidTempVarValueInURLQuery = ({key, value}) => ({
export const notifyInvalidTempVarValueInURLQuery = ({
key,
value,
}: TemplateUpdate): Notification => ({
...defaultErrorNotification,
icon: 'cube',
message: `Invalid URL query value of '${value}' supplied for template variable '${key}'.`,
})
export const notifyInvalidTimeRangeValueInURLQuery = () => ({
export const notifyInvalidTimeRangeValueInURLQuery = (): Notification => ({
...defaultErrorNotification,
icon: 'cube',
message: `Invalid URL query value supplied for lower or upper time range.`,
})
export const notifyInvalidZoomedTimeRangeValueInURLQuery = () => ({
export const notifyInvalidZoomedTimeRangeValueInURLQuery = (): Notification => ({
...defaultErrorNotification,
icon: 'cube',
message: `Invalid URL query value supplied for zoomed lower or zoomed upper time range.`,
})
export const notifyViewerUnauthorizedToSetTempVars = () => ({
export const notifyViewerUnauthorizedToSetTempVars = (): Notification => ({
...defaultErrorNotification,
message: `Viewer role unauthorized to override template variable values from URL.`,
})
// Rule Builder Notifications
// ----------------------------------------------------------------------------
export const notifyAlertRuleCreated = ruleName => ({
export const notifyAlertRuleCreated = (ruleName: string): Notification => ({
...defaultSuccessNotification,
message: `${ruleName} created successfully.`,
})
export const notifyAlertRuleCreateFailed = (ruleName, errorMessage) => ({
export const notifyAlertRuleCreateFailed = (
ruleName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
message: `There was a problem creating ${ruleName}: ${errorMessage}`,
})
export const notifyAlertRuleUpdated = ruleName => ({
export const notifyAlertRuleUpdated = (ruleName: string): Notification => ({
...defaultSuccessNotification,
message: `${ruleName} saved successfully.`,
})
export const notifyAlertRuleUpdateFailed = (ruleName, errorMessage) => ({
export const notifyAlertRuleUpdateFailed = (
ruleName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
message: `There was a problem saving ${ruleName}: ${errorMessage}`,
})
export const notifyAlertRuleDeleted = ruleName => ({
export const notifyAlertRuleDeleted = (ruleName: string): Notification => ({
...defaultSuccessNotification,
message: `${ruleName} deleted successfully.`,
})
export const notifyAlertRuleDeleteFailed = ruleName => ({
export const notifyAlertRuleDeleteFailed = (
ruleName: string
): Notification => ({
...defaultErrorNotification,
message: `${ruleName} could not be deleted.`,
})
export const notifyAlertRuleStatusUpdated = (ruleName, updatedStatus) => ({
export const notifyAlertRuleStatusUpdated = (
ruleName: string,
updatedStatus: string
): Notification => ({
...defaultSuccessNotification,
message: `${ruleName} ${updatedStatus} successfully.`,
})
export const notifyAlertRuleStatusUpdateFailed = (ruleName, updatedStatus) => ({
export const notifyAlertRuleStatusUpdateFailed = (
ruleName: string,
updatedStatus: string
): Notification => ({
...defaultSuccessNotification,
message: `${ruleName} could not be ${updatedStatus}.`,
})
export const notifyAlertRuleRequiresQuery = () =>
export const notifyAlertRuleRequiresQuery = (): string =>
'Please select a Database, Measurement, and Field.'
export const notifyAlertRuleRequiresConditionValue = () =>
export const notifyAlertRuleRequiresConditionValue = (): string =>
'Please enter a value in the Conditions section.'
export const notifyAlertRuleDeadmanInvalid = () =>
export const notifyAlertRuleDeadmanInvalid = (): string =>
'Deadman rules require a Database and Measurement.'
// Kapacitor Configuration Notifications
// ----------------------------------------------------------------------------
export const notifyKapacitorNameAlreadyTaken = kapacitorName => ({
export const notifyKapacitorNameAlreadyTaken = (
kapacitorName: string
): Notification => ({
...defaultErrorNotification,
message: `There is already a Kapacitor Connection named "${kapacitorName}".`,
})
export const notifyCouldNotFindKapacitor = () => ({
export const notifyCouldNotFindKapacitor = (): Notification => ({
...defaultErrorNotification,
message: 'We could not find a Kapacitor configuration for this source.',
})
export const notifyRefreshKapacitorFailed = () => ({
export const notifyRefreshKapacitorFailed = (): Notification => ({
...defaultErrorNotification,
message: 'There was an error getting the Kapacitor configuration.',
})
export const notifyAlertEndpointSaved = endpoint => ({
export const notifyAlertEndpointSaved = (endpoint: string): Notification => ({
...defaultSuccessNotification,
message: `Alert configuration for ${endpoint} saved successfully.`,
})
export const notifyAlertEndpointSaveFailed = (endpoint, errorMessage) => ({
export const notifyAlertEndpointSaveFailed = (
endpoint: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
message: `There was an error saving the alert configuration for ${endpoint}: ${errorMessage}`,
})
export const notifyAlertEndpointDeleteFailed = (
endpoint,
config,
errorMessage
) => ({
endpoint: string,
config: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification,
message: `There was an error deleting the alert configuration for ${endpoint}/${config}: ${errorMessage}`,
})
export const notifyAlertEndpointDeleted = (endpoint, config) => ({
export const notifyAlertEndpointDeleted = (
endpoint: string,
config: string
): Notification => ({
...defaultSuccessNotification,
message: `Alert configuration for ${endpoint}/${config} deleted successfully.`,
})
export const notifyTestAlertSent = endpoint => ({
export const notifyTestAlertSent = (endpoint: string): Notification => ({
...defaultSuccessNotification,
duration: TEN_SECONDS,
message: `Test Alert sent to ${endpoint}. If the Alert does not reach its destination, please check your endpoint configuration settings.`,
})
export const notifyTestAlertFailed = (endpoint, errorMessage?) => ({
export const notifyTestAlertFailed = (
endpoint: string,
errorMessage: string = ''
): Notification => ({
...defaultErrorNotification,
message: `There was an error sending a Test Alert to ${endpoint}${
errorMessage ? `: ${errorMessage}` : '.'
}`,
message: `There was an error sending a Test Alert to ${endpoint} ${errorMessage}.`,
})
export const notifyInvalidBatchSizeValue = () => ({
export const notifyInvalidBatchSizeValue = (): Notification => ({
...defaultErrorNotification,
message: 'Batch Size cannot be empty.',
})
export const notifyKapacitorConnectionFailed = () => ({
export const notifyKapacitorConnectionFailed = (): Notification => ({
...defaultErrorNotification,
message:
'Could not connect to Kapacitor. Check your connection settings in the Configuration page.',
})
export const notifyKapacitorCreated = () => ({
export const notifyKapacitorCreated = (): Notification => ({
...defaultSuccessNotification,
message:
'Connected to Kapacitor successfully! Configuring endpoints is optional.',
})
export const notifyKapacitorCreateFailed = () => ({
export const notifyKapacitorCreateFailed = (): Notification => ({
...defaultErrorNotification,
message: 'There was a problem connecting to Kapacitor.',
})
export const notifyKapacitorUpdated = () => ({
export const notifyKapacitorUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'Kapacitor Connection updated successfully.',
})
export const notifyKapacitorUpdateFailed = () => ({
export const notifyKapacitorUpdateFailed = (): Notification => ({
...defaultErrorNotification,
message: 'There was a problem updating the Kapacitor Connection.',
})
// TICKscript Notifications
// ----------------------------------------------------------------------------
export const notifyTickScriptCreated = () => ({
export const notifyTickScriptCreated = (): Notification => ({
...defaultSuccessNotification,
message: 'TICKscript successfully created.',
})
export const notifyTickscriptCreationFailed = () =>
export const notifyTickscriptCreationFailed = (): string =>
'Failed to create TICKscript.'
export const notifyTickscriptUpdated = () => ({
export const notifyTickscriptUpdated = (): Notification => ({
...defaultSuccessNotification,
message: 'TICKscript successfully updated.',
})
export const notifyTickscriptUpdateFailed = () => 'Failed to update TICKscript.'
export const notifyTickscriptUpdateFailed = (): string =>
'Failed to update TICKscript.'
export const notifyTickscriptLoggingUnavailable = () => ({
export const notifyTickscriptLoggingUnavailable = (): Notification => ({
type: 'warning',
icon: 'alert-triangle',
duration: INFINITE,
message: 'Kapacitor version 1.4 required to view TICKscript logs',
})
export const notifyTickscriptLoggingError = () => ({
export const notifyTickscriptLoggingError = (): Notification => ({
...defaultErrorNotification,
message: 'Could not collect kapacitor logs',
})
export const notifyKapacitorNotFound = () => ({
export const notifyKapacitorNotFound = (): Notification => ({
...defaultErrorNotification,
message: 'We could not find a Kapacitor configuration for this source.',
})
// Flux notifications
export const validateSuccess = () => ({
export const validateSuccess = (): Notification => ({
...defaultSuccessNotification,
message: 'No errors found. Happy Happy Joy Joy!',
})
export const notifyCopyToClipboardSuccess = text => ({
export const notifyCopyToClipboardSuccess = (text: string): Notification => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `'${text}' has been copied to clipboard.`,
})
export const notifyCopyToClipboardFailed = text => ({
export const notifyCopyToClipboardFailed = (text: string): Notification => ({
...defaultErrorNotification,
message: `'${text}' was not copied to clipboard.`,
})
// Service notifications
export const couldNotGetServices = {
export const couldNotGetServices: Notification = {
...defaultErrorNotification,
message: 'We could not get services',
}
export const fluxCreated = {
export const fluxCreated: Notification = {
...defaultSuccessNotification,
message: 'Flux Connection Created. Script your heart out!',
}
export const fluxNotCreated = (message: string) => ({
export const fluxNotCreated = (message: string): Notification => ({
...defaultErrorNotification,
message,
})
export const fluxNotUpdated = (message: string) => ({
export const fluxNotUpdated = (message: string): Notification => ({
...defaultErrorNotification,
message,
})
export const fluxUpdated = {
export const fluxUpdated: Notification = {
...defaultSuccessNotification,
message: 'Connection Updated. Rejoice!',
}
export const fluxTimeSeriesError = (message: string) => ({
export const fluxTimeSeriesError = (message: string): Notification => ({
...defaultErrorNotification,
message: `Could not get data: ${message}`,
})
export const fluxResponseTruncatedError = () => {
export const fluxResponseTruncatedError = (): Notification => {
const BYTES_TO_MB = 1 / 1e6
const APPROX_MAX_RESPONSE_MB = +(MAX_RESPONSE_BYTES * BYTES_TO_MB).toFixed(2)

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