Merge branch 'master' into toggle-tempvars-controls

pull/10616/head
Alex P 2017-05-22 10:41:27 -07:00
commit 590a33bce8
38 changed files with 779 additions and 163 deletions

8
.bumpversion.cfg Normal file
View File

@ -0,0 +1,8 @@
[bumpversion]
current_version = 1.3.0
files = README.md server/swagger.json
[bumpversion:file:ui/package.json]
search = "version": "{current_version}"
replace = "version": "{new_version}"

View File

@ -1,12 +1,30 @@
## v1.3.2 [unreleased]
### Bug Fixes
### Features
### UI Improvements
1. [#1508](https://github.com/influxdata/chronograf/pull/1508): The enter and escape keys now perform as expected when renaming dashboard headers.
## v1.3.1 [unreleased]
### Bug Fixes
1. [#1450](https://github.com/influxdata/chronograf/pull/1450): Fix infinite spinner when using "/chronograf" as a basepath
1. [#1428](https://github.com/influxdata/chronograf/issues/1428): Prevent Query Templates dropdown from appearing in the Cell Editor Overlay
1. [#1458](https://github.com/influxdata/chronograf/pull/1458): New versions of Chronograf will automatically clear localStorage settings until further notice.
1. [#1455](https://github.com/influxdata/chronograf/issues/1455): Fix backwards sort arrows in tables
1. [#1423](https://github.com/influxdata/chronograf/issues/1423): Make logout nav item consistent with design
1. [#1426](https://github.com/influxdata/chronograf/issues/1426): Fix graph loading spinner
1. [#1485](https://github.com/influxdata/chronograf/pull/1485): Filter out any template variable values that are empty, whitespace, or duplicates
1. [#1484](https://github.com/influxdata/chronograf/pull/1484): Allow user to select SingleStat as Visualization Type before adding any queries and continue to be able to click Add Query
1. [#1349](https://github.com/influxdata/chronograf/pull/1349): Add query for windows uptime
### Features
1. [#1477](https://github.com/influxdata/chronograf/pull/1477): Add ability to log alerts
1. [#1491](https://github.com/influxdata/chronograf/pull/1491): Update go vendoring to dep and committed vendor directory
1. [#1500](https://github.com/influxdata/chronograf/pull/1500): Add autocomplete functionality to Template Variable dropdowns
1. [#1498](https://github.com/influxdata/chronograf/pull/1498): Notify user via UI when local settings are cleared
### UI Improvements
1. [#1451](https://github.com/influxdata/chronograf/pull/1451): Refactor scrollbars to support non-webkit browsers
@ -15,6 +33,7 @@
1. [#1464](https://github.com/influxdata/chronograf/pull/1464): Make Template Variables Manager more space efficient
1. [#1464](https://github.com/influxdata/chronograf/pull/1464): Add page spinners to pages that did not have them
1. [#1464](https://github.com/influxdata/chronograf/pull/1464): Denote which source is connected in the Sources table
1. [#1478](https://github.com/influxdata/chronograf/pull/1478): InfluxDB dashboard uses milliseconds rather than nanoseconds
## v1.3.0 [2017-05-09]

19
Godeps
View File

@ -1,19 +0,0 @@
github.com/NYTimes/gziphandler 6710af535839f57c687b62c4c23d649f9545d885
github.com/Sirupsen/logrus 3ec0642a7fb6488f65b06f9040adc67e3990296a
github.com/boltdb/bolt 5cc10bbbc5c141029940133bb33c9e969512a698
github.com/bouk/httprouter ee8b3818a7f51fbc94cc709b5744b52c2c725e91
github.com/dgrijalva/jwt-go 24c63f56522a87ec5339cc3567883f1039378fdb
github.com/elazarl/go-bindata-assetfs 9a6736ed45b44bf3835afeebb3034b57ed329f3e
github.com/gogo/protobuf 6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b
github.com/google/go-github 1bc362c7737e51014af7299e016444b654095ad9
github.com/google/go-querystring 9235644dd9e52eeae6fa48efd539fdc351a0af53
github.com/influxdata/influxdb af72d9b0e4ebe95be30e89b160f43eabaf0529ed
github.com/influxdata/kapacitor 5408057e5a3493d3b5bd38d5d535ea45b587f8ff
github.com/influxdata/usage-client 6d3895376368aa52a3a81d2a16e90f0f52371967
github.com/jessevdk/go-flags 4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa
github.com/satori/go.uuid b061729afc07e77a8aa4fad0a2fd840958f1942a
github.com/sergi/go-diff 1d28411638c1e67fe1930830df207bef72496ae9
github.com/tylerb/graceful 50a48b6e73fcc75b45e22c05b79629a67c79e938
golang.org/x/net 749a502dd1eaf3e5bfd4f8956748c502357c0bbe
golang.org/x/oauth2 1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5
google.golang.org/api bc20c61134e1d25265dd60049f5735381e79b631

132
Gopkg.lock generated Normal file
View File

@ -0,0 +1,132 @@
memo = "bac138180cd86a0ae604cd3aa7b6ba300673478c880882bd58a4bd7f8bff518d"
[[projects]]
name = "github.com/NYTimes/gziphandler"
packages = ["."]
revision = "6710af535839f57c687b62c4c23d649f9545d885"
[[projects]]
name = "github.com/Sirupsen/logrus"
packages = ["."]
revision = "3ec0642a7fb6488f65b06f9040adc67e3990296a"
[[projects]]
name = "github.com/boltdb/bolt"
packages = ["."]
revision = "5cc10bbbc5c141029940133bb33c9e969512a698"
[[projects]]
name = "github.com/bouk/httprouter"
packages = ["."]
revision = "ee8b3818a7f51fbc94cc709b5744b52c2c725e91"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "24c63f56522a87ec5339cc3567883f1039378fdb"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "259d2a102b871d17f30e3cd9881a642961a1e486"
[[projects]]
name = "github.com/elazarl/go-bindata-assetfs"
packages = ["."]
revision = "9a6736ed45b44bf3835afeebb3034b57ed329f3e"
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["gogoproto","jsonpb","plugin/compare","plugin/defaultcheck","plugin/description","plugin/embedcheck","plugin/enumstringer","plugin/equal","plugin/face","plugin/gostring","plugin/marshalto","plugin/oneofcheck","plugin/populate","plugin/size","plugin/stringer","plugin/testgen","plugin/union","plugin/unmarshal","proto","protoc-gen-gogo","protoc-gen-gogo/descriptor","protoc-gen-gogo/generator","protoc-gen-gogo/grpc","protoc-gen-gogo/plugin","vanity","vanity/command"]
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["proto"]
revision = "8ee79997227bf9b34611aee7946ae64735e6fd93"
[[projects]]
name = "github.com/google/go-github"
packages = ["github"]
revision = "1bc362c7737e51014af7299e016444b654095ad9"
[[projects]]
name = "github.com/google/go-querystring"
packages = ["query"]
revision = "9235644dd9e52eeae6fa48efd539fdc351a0af53"
[[projects]]
name = "github.com/influxdata/influxdb"
packages = ["influxql","influxql/internal","influxql/neldermead","models","pkg/escape"]
revision = "af72d9b0e4ebe95be30e89b160f43eabaf0529ed"
[[projects]]
name = "github.com/influxdata/kapacitor"
packages = ["client/v1","influxdb","models","pipeline","services/k8s/client","tick","tick/ast","tick/stateful","udf"]
revision = "5408057e5a3493d3b5bd38d5d535ea45b587f8ff"
version = "v1.2.0"
[[projects]]
name = "github.com/influxdata/usage-client"
packages = ["v1"]
revision = "6d3895376368aa52a3a81d2a16e90f0f52371967"
[[projects]]
name = "github.com/jessevdk/go-flags"
packages = ["."]
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
[[projects]]
name = "github.com/jteeuwen/go-bindata"
packages = ["."]
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
[[projects]]
branch = "master"
name = "github.com/pkg/errors"
packages = ["."]
revision = "ff09b135c25aae272398c51a07235b90a75aa4f0"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "b061729afc07e77a8aa4fad0a2fd840958f1942a"
[[projects]]
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
revision = "1d28411638c1e67fe1930830df207bef72496ae9"
[[projects]]
name = "github.com/tylerb/graceful"
packages = ["."]
revision = "50a48b6e73fcc75b45e22c05b79629a67c79e938"
version = "v1.2.13"
[[projects]]
name = "golang.org/x/net"
packages = ["context","context/ctxhttp"]
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
[[projects]]
name = "golang.org/x/oauth2"
packages = [".","github","heroku","internal"]
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a"
[[projects]]
name = "google.golang.org/api"
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
[[projects]]
name = "google.golang.org/appengine"
packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"

77
Gopkg.toml Normal file
View File

@ -0,0 +1,77 @@
required = ["github.com/jteeuwen/go-bindata","github.com/gogo/protobuf/proto","github.com/gogo/protobuf/jsonpb","github.com/gogo/protobuf/protoc-gen-gogo","github.com/gogo/protobuf/gogoproto"]
[[dependencies]]
name = "github.com/NYTimes/gziphandler"
revision = "6710af535839f57c687b62c4c23d649f9545d885"
[[dependencies]]
name = "github.com/Sirupsen/logrus"
revision = "3ec0642a7fb6488f65b06f9040adc67e3990296a"
[[dependencies]]
name = "github.com/boltdb/bolt"
revision = "5cc10bbbc5c141029940133bb33c9e969512a698"
[[dependencies]]
name = "github.com/bouk/httprouter"
revision = "ee8b3818a7f51fbc94cc709b5744b52c2c725e91"
[[dependencies]]
name = "github.com/dgrijalva/jwt-go"
revision = "24c63f56522a87ec5339cc3567883f1039378fdb"
[[dependencies]]
name = "github.com/elazarl/go-bindata-assetfs"
revision = "9a6736ed45b44bf3835afeebb3034b57ed329f3e"
[[dependencies]]
name = "github.com/gogo/protobuf"
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
[[dependencies]]
name = "github.com/google/go-github"
revision = "1bc362c7737e51014af7299e016444b654095ad9"
[[dependencies]]
name = "github.com/influxdata/influxdb"
revision = "af72d9b0e4ebe95be30e89b160f43eabaf0529ed"
[[dependencies]]
name = "github.com/influxdata/kapacitor"
version = "^1.2.0"
[[dependencies]]
name = "github.com/influxdata/usage-client"
revision = "6d3895376368aa52a3a81d2a16e90f0f52371967"
[[dependencies]]
name = "github.com/jessevdk/go-flags"
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
[[dependencies]]
name = "github.com/jteeuwen/go-bindata"
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
[[dependencies]]
name = "github.com/satori/go.uuid"
revision = "b061729afc07e77a8aa4fad0a2fd840958f1942a"
[[dependencies]]
name = "github.com/sergi/go-diff"
revision = "1d28411638c1e67fe1930830df207bef72496ae9"
[[dependencies]]
name = "github.com/tylerb/graceful"
version = "^1.2.13"
[[dependencies]]
name = "golang.org/x/net"
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
[[dependencies]]
name = "golang.org/x/oauth2"
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
[[dependencies]]
name = "google.golang.org/api"
revision = "bc20c61134e1d25265dd60049f5735381e79b631"

View File

@ -2,11 +2,10 @@
VERSION ?= $(shell git describe --always --tags)
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
GDM := $(shell command -v gdm 2> /dev/null)
GOBINDATA := $(shell go list -f {{.Root}} github.com/jteeuwen/go-bindata 2> /dev/null)
YARN := $(shell command -v yarn 2> /dev/null)
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go')
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go' -not -path "./vendor/*" )
UISOURCES := $(shell find ui -type f -not \( -path ui/build/\* -o -path ui/node_modules/\* -prune \) )
LDFLAGS=-ldflags "-s -X main.version=${VERSION} -X main.commit=${COMMIT}"
@ -70,16 +69,11 @@ canned/bin_gen.go: canned/*.json
dep: .jsdep .godep
.godep: Godeps
ifndef GDM
@echo "Installing GDM"
go get github.com/sparrc/gdm
endif
.godep:
ifndef GOBINDATA
@echo "Installing go-bindata"
go get -u github.com/jteeuwen/go-bindata/...
endif
gdm restore
@touch .godep
.jsdep: ui/yarn.lock
@ -90,16 +84,18 @@ else
@touch .jsdep
endif
gen: bolt/internal/internal.proto
gen: internal.pb.go
internal.pb.go: bolt/internal/internal.proto
go generate -x ./bolt/internal
test: jstest gotest gotestrace
gotest:
go test ./...
go test `go list ./... | grep -v /vendor/`
gotestrace:
go test -race ./...
go test -race `go list ./... | grep -v /vendor/`
jstest:
cd ui && npm test
@ -117,8 +113,5 @@ clean:
rm -f dist/dist_gen.go canned/bin_gen.go server/swagger_gen.go
@rm -f .godep .jsdep .jssrc .dev-jssrc .bindata
continuous:
while true; do if fswatch -e "\.git" -r --one-event .; then echo "#-> Starting build: `date`"; make dev; pkill -9 chronograf; make run-dev & echo "#-> Build complete."; fi; sleep 0.5; done
ctags:
ctags -R --languages="Go" --exclude=.git --exclude=ui .

View File

@ -110,8 +110,7 @@ Change the default root path of the Chronograf server with the `--basepath` opti
## Versions
The most recent version of Chronograf is v1.3.0.
We will be iterating quickly based on user feedback and recommend using the [nightly builds](https://www.influxdata.com/downloads/) for the time being.
The most recent version of Chronograf is [v1.3.0](https://www.influxdata.com/downloads/).
Spotted a bug or have a feature request?
Please open [an issue](https://github.com/influxdata/chronograf/issues/new)!
@ -136,16 +135,16 @@ By default, chronograf runs on port `8888`.
### With Docker
To get started right away with Docker, you can pull down our latest alpha:
To get started right away with Docker, you can pull down our latest release:
```sh
docker pull quay.io/influxdb/chronograf:latest
docker pull chronograf:1.3.0
```
### From Source
* Chronograf works with go 1.7.x, node 6.x/7.x, and yarn 0.18+.
* Chronograf requires [Kapacitor](https://github.com/influxdata/kapacitor) 1.1.x+ to create and store alerts.
* Chronograf works with go 1.8.x, node 6.x/7.x, and yarn 0.18+.
* Chronograf requires [Kapacitor](https://github.com/influxdata/kapacitor) 1.2.x+ to create and store alerts.
1. [Install Go](https://golang.org/doc/install)
1. [Install Node and NPM](https://nodejs.org/en/download/)

View File

@ -13,16 +13,18 @@
"name": "InfluxDB - Query Performance",
"queries": [
{
"query": "SELECT non_negative_derivative(max(\"queryDurationNs\"), 1s) AS \"duration\" FROM \"influxdb_queryExecutor\"",
"query": "SELECT non_negative_derivative(max(\"queryDurationNs\"), 1s) / 1000000 AS \"duration_ms\" FROM \"influxdb_queryExecutor\"",
"label": "ms",
"groupbys": [],
"wheres": []
},
{
"query": "SELECT non_negative_derivative(max(\"queriesExecuted\"), 1s) AS \"queries_executed\" FROM \"influxdb_queryExecutor\"",
"query": "SELECT non_negative_derivative(max(\"queriesExecuted\"), 1s) / 1000000 AS \"queries_executed_ms\" FROM \"influxdb_queryExecutor\"",
"label": "ms",
"groupbys": [],
"wheres": []
}
]
}
]
}
}

View File

@ -3,7 +3,7 @@ machine:
services:
- docker
environment:
DOCKER_TAG: chronograf-20170208
DOCKER_TAG: chronograf-20170516
dependencies:
override:

View File

@ -1,15 +1,40 @@
## Creating a release
The release process is handled via our [circle.yml](https://github.com/influxdata/chronograf/blob/master/circle.yml).
A release tag of the format `1.1.0-beta1` needs to be added. Afterwhich, circle
A release tag of the format `1.3.0` needs to be added. Afterwhich, circle
will build our packages for all of our platforms.
### Bumpversion
We use [bumpversion](https://github.com/peritus/bumpversion) to help us
remember all the places to increment our version number.
To install:
```sh
pip install --upgrade bumpversion
```
To use to increment third number (e.g. 1.3.0 -> 1.3.1):
```sh
bumpversion patch
```
To increment minor number (e.g. 1.3.5 -> 1.4.0):
```sh
bumpversion minor
```
The behavior of `bumpversion` is controlled by .bumpversion.cfg
### Creating Release tag
You can create a release tag from [Github](https://github.com/influxdata/chronograf/releases)
or create an annotated tag:
```sh
git tag -a 1.1.0-beta1 -m "Release 1.1.0-beta1"
git tag -a 1.3.0 -m "Release 1.3.0"
git push --tags
```
@ -22,7 +47,7 @@ git push --tags
* armel
* static_i386
* static_amd64
* OS X
* OS X
* amd64
* Windows
* amd64

View File

@ -18,9 +18,10 @@ RUN pip install boto requests python-jose --upgrade
RUN gem install fpm
# Install node
RUN wget -q https://nodejs.org/dist/latest-v6.x/node-v6.9.5-linux-x64.tar.gz; \
tar -xvf node-v6.9.5-linux-x64.tar.gz -C / --strip-components=1; \
rm -f node-v6.9.5-linux-x64.tar.gz
ENV NODE_VERSION v6.10.3
RUN wget -q https://nodejs.org/dist/latest-v6.x/node-${NODE_VERSION}-linux-x64.tar.gz; \
tar -xvf node-${NODE_VERSION}-linux-x64.tar.gz -C / --strip-components=1; \
rm -f node-${NODE_VERSION}-linux-x64.tar.gz
# Update npm
RUN cd $(npm root -g)/npm \
@ -34,7 +35,7 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
# Install go
ENV GOPATH /root/go
ENV GO_VERSION 1.7.5
ENV GO_VERSION 1.8.1
ENV GO_ARCH amd64
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \

View File

@ -39,7 +39,7 @@ CONFIGURATION_FILES = [
LOGROTATE_DIR + '/chronograf',
]
PACKAGE_LICENSE = "MIT"
PACKAGE_LICENSE = "AGPLv3"
PACKAGE_URL = "https://github.com/influxdata/chronograf"
MAINTAINER = "contact@influxdb.com"
VENDOR = "InfluxData"

View File

@ -21,6 +21,10 @@ func Convert(influxQL string) (chronograf.QueryConfig, error) {
return chronograf.QueryConfig{}, err
}
if itsDashboardTime {
influxQL = strings.Replace(influxQL, "now() - 15m", ":dashboardTime:", 1)
}
raw := chronograf.QueryConfig{
RawText: &influxQL,
Fields: []chronograf.Field{},

View File

@ -21,7 +21,7 @@ func kapaHandler(handler string) (string, error) {
return "email", nil
case "http":
return "post", nil
case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec":
case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec", "log":
return handler, nil
default:
return "", fmt.Errorf("Unsupported alert handler %s", handler)

View File

@ -95,6 +95,31 @@ func TestAlertServices(t *testing.T) {
},
wantErr: true,
},
{
name: "Test log",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "log",
Args: []string{"/tmp/alerts.log"},
},
},
},
want: `alert()
.log('/tmp/alerts.log')
`,
},
{
name: "Test log no argument",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "log",
},
},
},
wantErr: true,
},
{
name: "Test tcp no argument with other services",
rule: chronograf.AlertRule{

View File

@ -493,6 +493,7 @@ func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) {
extractTalk(t, rule)
extractTelegram(t, rule)
extractTCP(t, rule)
extractLog(t, rule)
extractExec(t, rule)
}
return nil
@ -801,6 +802,23 @@ func extractTCP(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
rule.AlertNodes = append(rule.AlertNodes, alert)
}
func extractLog(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
if node.LogHandlers == nil {
return
}
rule.Alerts = append(rule.Alerts, "log")
log := node.LogHandlers[0]
alert := chronograf.KapacitorNode{
Name: "log",
}
if log.FilePath != "" {
alert.Args = []string{log.FilePath}
}
rule.AlertNodes = append(rule.AlertNodes, alert)
}
func extractExec(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
if node.ExecHandlers == nil {
return

View File

@ -59,12 +59,13 @@ func TestReverse(t *testing.T) {
.slack()
.victorOps()
.email('howdy@howdy.com')
.log('/tmp/alerts.log')
`),
want: chronograf.AlertRule{
Name: "name",
Trigger: "threshold",
Alerts: []string{"victorops", "smtp", "slack"},
Alerts: []string{"victorops", "smtp", "slack", "log"},
AlertNodes: []chronograf.KapacitorNode{
{
Name: "victorops",
@ -76,6 +77,10 @@ func TestReverse(t *testing.T) {
{
Name: "slack",
},
{
Name: "log",
Args: []string{"/tmp/alerts.log"},
},
},
TriggerValues: chronograf.TriggerValues{
Operator: "greater than",

View File

@ -34,15 +34,16 @@ const AlertsTable = React.createClass({
filterAlerts(searchTerm, newAlerts) {
const alerts = newAlerts || this.props.alerts
const filterText = searchTerm.toLowerCase()
const filteredAlerts = alerts.filter(h => {
if (h.host === null || h.name === null || h.level === null) {
return false
}
return (
h.name.toLowerCase().search(searchTerm.toLowerCase()) !== -1 ||
h.host.toLowerCase().search(searchTerm.toLowerCase()) !== -1 ||
h.level.toLowerCase().search(searchTerm.toLowerCase()) !== -1
h.name.toLowerCase().includes(filterText) ||
h.host.toLowerCase().includes(filterText) ||
h.level.toLowerCase().includes(filterText)
)
})
this.setState({searchTerm, filteredAlerts})

View File

@ -8,12 +8,27 @@ class DashboardEditHeader extends Component {
const {dashboard: {name}} = props
this.state = {name}
this.handleChange = ::this.handleChange
this.handleFormSubmit = ::this.handleFormSubmit
this.handleKeyUp = ::this.handleKeyUp
}
handleChange(name) {
this.setState({name})
}
handleFormSubmit(e) {
e.preventDefault()
const name = e.target.name.value
this.props.onSave(name)
}
handleKeyUp(e) {
const {onCancel} = this.props
if (e.key === 'Escape') {
onCancel()
}
}
render() {
const {onSave, onCancel} = this.props
const {name} = this.state
@ -21,15 +36,18 @@ class DashboardEditHeader extends Component {
return (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
<form className="page-header__left" onSubmit={this.handleFormSubmit}>
<input
className="page-header--editing"
name="name"
autoFocus={true}
value={name}
placeholder="Name this Dashboard"
onChange={e => this.handleChange(e.target.value)}
onKeyUp={this.handleKeyUp}
autoComplete="off"
/>
</div>
</form>
<ConfirmButtons item={name} onConfirm={onSave} onCancel={onCancel} />
</div>
</div>

View File

@ -28,7 +28,8 @@ const TemplateControlBar = ({
<Dropdown
items={items}
buttonSize="btn-xs"
selected={selectedText || 'Loading...'}
useAutoComplete={true}
selected={selectedText || '(No values)'}
onChoose={item =>
onSelectTemplate(id, [item].map(x => omit(x, 'text')))}
/>

View File

@ -2,6 +2,8 @@ import React, {PropTypes, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import uniq from 'lodash/uniq'
import OnClickOutside from 'react-onclickoutside'
import classnames from 'classnames'
@ -23,6 +25,8 @@ import generateTemplateVariableQuery
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
const compact = values => uniq(values).filter(value => /\S/.test(value))
const RowValues = ({
selectedType,
values = [],
@ -30,7 +34,7 @@ const RowValues = ({
onStartEdit,
autoFocusTarget,
}) => {
const _values = values.map(({value}) => value).join(', ')
const _values = values.map(v => v.value).join(', ')
if (selectedType === 'csv') {
return (
@ -293,7 +297,8 @@ class RowWrapper extends Component {
} else {
parsedData = await this.runTemplateVariableQuery(source, queryConfig)
}
onRunQuerySuccess(template, queryConfig, parsedData, tempVar)
onRunQuerySuccess(template, queryConfig, compact(parsedData), tempVar)
} catch (error) {
onRunQueryFailure(error)
}

View File

@ -115,8 +115,9 @@ const MeasurementList = React.createClass({
)
}
const filterText = this.state.filterText.toLowerCase()
const measurements = this.state.measurements.filter(m =>
m.match(this.state.filterText)
m.toLowerCase().includes(filterText)
)
return (

View File

@ -101,6 +101,7 @@ const QueryBuilder = React.createClass({
config={query}
onUpdate={this.handleEditRawText}
templates={templates}
isInDataExplorer={isInDataExplorer}
/>
{this.renderLists()}
</div>

View File

@ -162,9 +162,12 @@ class QueryEditor extends Component {
if (matched && !_.isEmpty(templates)) {
// maintain cursor poition
const start = this.editor.selectionStart
const end = this.editor.selectionEnd
const filterText = matched[0].substr(1).toLowerCase()
const filteredTemplates = templates.filter(t =>
t.tempVar.includes(matched[0].substring(1))
t.tempVar.toLowerCase().includes(filterText)
)
const found = filteredTemplates.find(
@ -241,15 +244,19 @@ class QueryEditor extends Component {
}
renderStatus(status) {
const {isInDataExplorer} = this.props
if (!status) {
return (
<div className="query-editor--status">
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
{isInDataExplorer
? <Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
: null}
</div>
)
}
@ -258,12 +265,14 @@ class QueryEditor extends Component {
return (
<div className="query-editor--status">
<LoadingDots />
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
{isInDataExplorer
? <Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
: null}
</div>
)
}
@ -286,23 +295,26 @@ class QueryEditor extends Component {
/>
{status.error || status.warn || status.success}
</span>
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
{isInDataExplorer
? <Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
: null}
</div>
)
}
}
const {arrayOf, func, shape, string} = PropTypes
const {arrayOf, bool, func, shape, string} = PropTypes
QueryEditor.propTypes = {
query: string.isRequired,
onUpdate: func.isRequired,
config: shape().isRequired,
isInDataExplorer: bool,
templates: arrayOf(
shape({
tempVar: string.isRequired,

View File

@ -52,7 +52,8 @@ const TagListItem = React.createClass({
return <div>no tag values</div>
}
const filtered = tagValues.filter(v => v.match(this.state.filterText))
const filterText = this.state.filterText.toLowerCase()
const filtered = tagValues.filter(v => v.toLowerCase().includes(filterText))
return (
<div className="query-builder--sub-list">

View File

@ -43,7 +43,7 @@ const VisView = ({
if (cellType === 'single-stat') {
return (
<RefreshingSingleStat
queries={[queries[0]]}
queries={queries.length ? [queries[0]] : []}
autoRefresh={autoRefresh}
templates={templates}
/>

View File

@ -5,17 +5,24 @@ import _ from 'lodash'
export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
return proxy({
source: proxyLink,
query: 'select mean(usage_user) from cpu where cpu = \'cpu-total\' and time > now() - 10m group by host; select mean("load1") from "system" where time > now() - 10m group by host; select mean("Percent_Processor_Time") from win_cpu where time > now() - 10m group by host; select mean("Processor_Queue_Length") from win_system where time > now() - 10s group by host; select non_negative_derivative(mean(uptime)) as deltaUptime from "system" where time > now() - 10m group by host, time(1m) fill(0); show tag values from /win_system|system/ with key = "host"',
query: `select mean(usage_user) from cpu where cpu = "cpu-total" and time > now() - 10m group by host;
select mean("load1") from "system" where time > now() - 10m group by host;
select non_negative_derivative(mean(uptime)) as deltaUptime from "system" where time > now() - 10m group by host, time(1m) fill(0);
select mean("Percent_Processor_Time") from win_cpu where time > now() - 10m group by host;
select mean("Processor_Queue_Length") from win_system where time > now() - 10s group by host;
select non_negative_derivative(mean("System_Up_Time")) as deltaUptime from "telegraf"."autogen"."win_uptime" where time > now() - 10m group by host, time(1m) fill(0);
show tag values from /win_system|system/ with key = "host"`,
db: telegrafDB,
}).then(resp => {
const hosts = {}
const precision = 100
const cpuSeries = _.get(resp, ['data', 'results', '0', 'series'], [])
const loadSeries = _.get(resp, ['data', 'results', '1', 'series'], [])
const winCPUSeries = _.get(resp, ['data', 'results', '2', 'series'], [])
const winLoadSeries = _.get(resp, ['data', 'results', '3', 'series'], [])
const uptimeSeries = _.get(resp, ['data', 'results', '4', 'series'], [])
const allHostsSeries = _.get(resp, ['data', 'results', '5', 'series'], [])
const uptimeSeries = _.get(resp, ['data', 'results', '2', 'series'], [])
const winCPUSeries = _.get(resp, ['data', 'results', '3', 'series'], [])
const winLoadSeries = _.get(resp, ['data', 'results', '4', 'series'], [])
const winUptimeSeries = _.get(resp, ['data', 'results', '5', 'series'], [])
const allHostsSeries = _.get(resp, ['data', 'results', '6', 'series'], [])
allHostsSeries.forEach(s => {
const hostnameIndex = s.columns.findIndex(col => col === 'value')
@ -64,6 +71,12 @@ export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
Math.round(s.values[0][meanIndex] * precision) / precision
})
winUptimeSeries.forEach(s => {
const uptimeIndex = s.columns.findIndex(col => col === 'deltaUptime')
hosts[s.tags.host].deltaUptime =
s.values[s.values.length - 1][uptimeIndex]
})
return hosts
})
}

View File

@ -35,20 +35,21 @@ const HostsTable = React.createClass({
},
filter(allHosts, searchTerm) {
const filterText = searchTerm.toLowerCase()
return allHosts.filter(h => {
const apps = h.apps ? h.apps.join(', ') : ''
// search each tag for the presence of the search term
let tagResult = false
if (h.tags) {
tagResult = Object.keys(h.tags).reduce((acc, key) => {
return acc || h.tags[key].search(searchTerm) !== -1
return acc || h.tags[key].toLowerCase().includes(filterText)
}, false)
} else {
tagResult = false
}
return (
h.name.search(searchTerm) !== -1 ||
apps.search(searchTerm) !== -1 ||
h.name.toLowerCase().includes(filterText) ||
apps.toLowerCase().includes(filterText) ||
tagResult
)
})

View File

@ -40,6 +40,8 @@ import 'src/style/chronograf.scss'
import {HEARTBEAT_INTERVAL} from 'shared/constants'
const errorsQueue = []
const rootNode = document.getElementById('react-root')
const basepath = rootNode.dataset.basepath || ''
@ -48,7 +50,7 @@ const browserHistory = useRouterHistory(createHistory)({
basename: basepath, // this is written in when available by the URL prefixer middleware
})
const store = configureStore(loadLocalStorage(), browserHistory)
const store = configureStore(loadLocalStorage(errorsQueue), browserHistory)
const {dispatch} = store
browserHistory.listen(() => {
@ -67,6 +69,7 @@ const history = syncHistoryWithStore(browserHistory, store)
const Root = React.createClass({
componentWillMount() {
this.flushErrorsQueue()
this.checkAuth()
},
@ -99,6 +102,14 @@ const Root = React.createClass({
}
},
flushErrorsQueue() {
if (errorsQueue.length) {
errorsQueue.forEach(errorText => {
dispatch(errorThrown({status: 0, auth: null}, errorText, 'warning'))
})
}
},
render() {
return (
<Provider store={store}>

View File

@ -74,12 +74,13 @@ export const RULE_MESSAGE_TEMPLATES = {
},
}
export const DEFAULT_ALERTS = ['http', 'tcp', 'exec']
export const DEFAULT_ALERTS = ['http', 'tcp', 'exec', 'log']
export const DEFAULT_ALERT_LABELS = {
http: 'URL:',
tcp: 'Address:',
exec: 'Add Command (Arguments separated by Spaces):',
log: 'File',
smtp: 'Email Addresses (Separated by Spaces):',
slack: 'Send alerts to Slack channel:',
alerta: 'Paste Alerta TICKscript:',
@ -88,6 +89,7 @@ export const DEFAULT_ALERT_PLACEHOLDERS = {
http: 'Ex: http://example.com/api/alert',
tcp: 'Ex: exampleendpoint.com:5678',
exec: 'Ex: woogie boogie',
log: 'Ex: /tmp/alerts.log',
smtp: 'Ex: benedict@domain.com delaney@domain.com susan@domain.com',
slack: '#alerts',
alerta: 'alerta()',
@ -97,6 +99,7 @@ export const ALERT_NODES_ACCESSORS = {
http: rule => _.get(rule, 'alertNodes[0].args[0]', ''),
tcp: rule => _.get(rule, 'alertNodes[0].args[0]', ''),
exec: rule => _.get(rule, 'alertNodes[0].args', []).join(' '),
log: rule => _.get(rule, 'alertNodes[0].args[0]', ''),
smtp: rule => _.get(rule, 'alertNodes[0].args', []).join(' '),
slack: rule => _.get(rule, 'alertNodes[0].properties[0].args', ''),
alerta: rule =>

View File

@ -81,6 +81,7 @@ export default function rules(state = {}, action) {
switch (alertType) {
case 'http':
case 'tcp':
case 'log':
alertNodesByType = [
{
name: alertType,

View File

@ -1,10 +1,30 @@
export const loadLocalStorage = () => {
export const loadLocalStorage = errorsQueue => {
try {
const serializedState = localStorage.getItem('state')
return JSON.parse(serializedState) || {}
} catch (err) {
console.error(`Loading persisted state failed: ${err}`) // eslint-disable-line no-console
const state = JSON.parse(serializedState)
// eslint-disable-next-line no-undef
if (state.VERSION !== VERSION) {
const errorText =
'New version of Chronograf detected. Local settings cleared.'
console.log(errorText) // eslint-disable-line no-console
errorsQueue.push(errorText)
window.localStorage.removeItem('state')
return {}
}
delete state.VERSION
return state || {}
} catch (error) {
const errorText = `Loading local settings failed: ${error}`
console.error(errorText) // eslint-disable-line no-console
errorsQueue.push(errorText)
return {}
}
}
@ -25,6 +45,7 @@ export const saveToLocalStorage = ({
queryConfigs,
timeRange,
dataExplorer,
VERSION, // eslint-disable-line no-undef
})
)
} catch (err) {

View File

@ -1,5 +1,6 @@
export const errorThrown = (error, altText) => ({
export const errorThrown = (error, altText, alertType) => ({
type: 'ERROR_THROWN',
error,
altText,
alertType,
})

View File

@ -32,7 +32,7 @@ export const handleSuccess = (data, query, editQueryStatus) => {
}
export const handleError = (error, query, editQueryStatus) => {
const message = _.get(error, ['data', 'message'], error)
const message = _.get(error, ['data', 'message'], error.toString())
// 400 from chrono server = fail
editQueryStatus(query.id, {error: message})

View File

@ -3,13 +3,19 @@ import {Link} from 'react-router'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DROPDOWN_MENU_MAX_HEIGHT, DROPDOWN_MENU_ITEM_THRESHOLD} from 'shared/constants/index'
import {
DROPDOWN_MENU_MAX_HEIGHT,
DROPDOWN_MENU_ITEM_THRESHOLD,
} from 'shared/constants/index'
class Dropdown extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
searchTerm: '',
filteredItems: this.props.items,
highlightedItemIndex: null,
}
this.handleClickOutside = ::this.handleClickOutside
@ -17,6 +23,10 @@ class Dropdown extends Component {
this.handleSelection = ::this.handleSelection
this.toggleMenu = ::this.toggleMenu
this.handleAction = ::this.handleAction
this.handleFilterChange = ::this.handleFilterChange
this.applyFilter = ::this.applyFilter
this.handleFilterKeyPress = ::this.handleFilterKeyPress
this.handleHighlight = ::this.handleHighlight
}
static defaultProps = {
@ -24,6 +34,7 @@ class Dropdown extends Component {
buttonSize: 'btn-sm',
buttonColor: 'btn-info',
menuWidth: '100%',
useAutoComplete: false,
}
handleClickOutside() {
@ -42,10 +53,21 @@ class Dropdown extends Component {
this.props.onChoose(item)
}
handleHighlight(itemIndex) {
this.setState({highlightedItemIndex: itemIndex})
}
toggleMenu(e) {
if (e) {
e.stopPropagation()
}
if (!this.state.isOpen) {
this.setState({
searchTerm: '',
filteredItems: this.props.items,
highlightedItemIndex: null,
})
}
this.setState({isOpen: !this.state.isOpen})
}
@ -54,21 +76,94 @@ class Dropdown extends Component {
action.handler(item)
}
handleFilterKeyPress(e) {
const {filteredItems, highlightedItemIndex} = this.state
if (e.key === 'Enter' && filteredItems.length) {
this.setState({isOpen: false})
this.props.onChoose(filteredItems[highlightedItemIndex])
}
if (e.key === 'Escape') {
this.setState({isOpen: false})
}
if (e.key === 'ArrowUp' && highlightedItemIndex > 0) {
this.setState({highlightedItemIndex: highlightedItemIndex - 1})
}
if (e.key === 'ArrowDown') {
if (highlightedItemIndex < filteredItems.length - 1) {
this.setState({highlightedItemIndex: highlightedItemIndex + 1})
}
if (highlightedItemIndex === null && filteredItems.length) {
this.setState({highlightedItemIndex: 0})
}
}
}
handleFilterChange(e) {
if (e.target.value === null || e.target.value === '') {
this.setState({
searchTerm: '',
filteredItems: this.props.items,
highlightedItemIndex: null,
})
} else {
this.setState({searchTerm: e.target.value}, () =>
this.applyFilter(this.state.searchTerm)
)
}
}
applyFilter(searchTerm) {
const {items} = this.props
const filterText = searchTerm.toLowerCase()
const matchingItems = items.filter(item =>
item.text.toLowerCase().includes(filterText)
)
this.setState({
filteredItems: matchingItems,
highlightedItemIndex: 0,
})
}
renderShortMenu() {
const {actions, addNew, items, menuWidth, menuLabel} = this.props
const {
actions,
addNew,
items,
menuWidth,
menuLabel,
useAutoComplete,
selected,
} = this.props
const {filteredItems, highlightedItemIndex} = this.state
const menuItems = useAutoComplete ? filteredItems : items
return (
<ul className="dropdown-menu" style={{width: menuWidth}}>
{menuLabel
? <li className="dropdown-header">{menuLabel}</li>
: null
}
{items.map((item, i) => {
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
})}
style={{width: menuWidth}}
>
{menuLabel ? <li className="dropdown-header">{menuLabel}</li> : null}
{menuItems.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} role="separator" className="divider" />
}
return (
<li className="dropdown-item" key={i}>
<a href="#" onClick={() => this.handleSelection(item)}>
<li
className={classnames('dropdown-item', {
highlight: i === highlightedItemIndex,
active: item.text === selected,
})}
key={i}
>
<a
href="#"
onClick={() => this.handleSelection(item)}
onMouseOver={() => this.handleHighlight(i)}
>
{item.text}
</a>
{actions.length > 0
@ -78,8 +173,7 @@ class Dropdown extends Component {
<button
key={action.text}
className="dropdown-item__action"
onClick={e =>
this.handleAction(e, action, item)}
onClick={e => this.handleAction(e, action, item)}
>
<span
title={action.text}
@ -105,21 +199,44 @@ class Dropdown extends Component {
}
renderLongMenu() {
const {actions, addNew, items, menuWidth, menuLabel} = this.props
const {
actions,
addNew,
items,
menuWidth,
menuLabel,
useAutoComplete,
selected,
} = this.props
const {filteredItems, highlightedItemIndex} = this.state
const menuItems = useAutoComplete ? filteredItems : items
return (
<ul className="dropdown-menu" style={{width: menuWidth, height: DROPDOWN_MENU_MAX_HEIGHT}}>
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
})}
style={{width: menuWidth, height: DROPDOWN_MENU_MAX_HEIGHT}}
>
<FancyScrollbar autoHide={false}>
{menuLabel
? <li className="dropdown-header">{menuLabel}</li>
: null
}
{items.map((item, i) => {
{menuLabel ? <li className="dropdown-header">{menuLabel}</li> : null}
{menuItems.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} role="separator" className="divider" />
}
return (
<li className="dropdown-item" key={i}>
<a href="#" onClick={() => this.handleSelection(item)}>
<li
className={classnames('dropdown-item', {
highlight: i === highlightedItemIndex,
active: item.text === selected,
})}
key={i}
>
<a
href="#"
onClick={() => this.handleSelection(item)}
onMouseOver={() => this.handleHighlight(i)}
>
{item.text}
</a>
{actions.length > 0
@ -129,8 +246,7 @@ class Dropdown extends Component {
<button
key={action.text}
className="dropdown-item__action"
onClick={e =>
this.handleAction(e, action, item)}
onClick={e => this.handleAction(e, action, item)}
>
<span
title={action.text}
@ -164,33 +280,57 @@ class Dropdown extends Component {
iconName,
buttonSize,
buttonColor,
useAutoComplete,
} = this.props
const {isOpen} = this.state
const {isOpen, searchTerm, filteredItems} = this.state
const menuItems = useAutoComplete ? filteredItems : items
return (
<div
onClick={this.handleClick}
className={classnames(`dropdown ${className}`, {open: isOpen})}
>
<div className={`btn dropdown-toggle ${buttonSize} ${buttonColor}`}>
{iconName
? <span className={classnames('icon', {[iconName]: true})} />
: null}
<span className="dropdown-selected">{selected}</span>
<span className="caret" />
</div>
{(isOpen && items.length < DROPDOWN_MENU_ITEM_THRESHOLD)
{useAutoComplete && isOpen
? <div
className={`dropdown-autocomplete dropdown-toggle ${buttonSize} ${buttonColor}`}
>
<input
ref="dropdownAutoComplete"
className="dropdown-autocomplete--input"
type="text"
autoFocus={true}
placeholder="Filter items..."
spellCheck={false}
onChange={this.handleFilterChange}
onKeyDown={this.handleFilterKeyPress}
value={searchTerm}
/>
<span className="caret" />
</div>
: <div className={`btn dropdown-toggle ${buttonSize} ${buttonColor}`}>
{iconName
? <span className={classnames('icon', {[iconName]: true})} />
: null}
<span className="dropdown-selected">{selected}</span>
<span className="caret" />
</div>}
{isOpen && menuItems.length < DROPDOWN_MENU_ITEM_THRESHOLD
? this.renderShortMenu()
: null}
{(isOpen && items.length >= DROPDOWN_MENU_ITEM_THRESHOLD)
{isOpen && menuItems.length >= DROPDOWN_MENU_ITEM_THRESHOLD
? this.renderLongMenu()
: null}
{isOpen && !menuItems.length
? <ul className="dropdown-menu">
<li className="dropdown-empty">No matching items</li>
</ul>
: null}
</div>
)
}
}
const {arrayOf, shape, string, func} = PropTypes
const {arrayOf, bool, shape, string, func} = PropTypes
Dropdown.propTypes = {
actions: arrayOf(
@ -218,6 +358,7 @@ Dropdown.propTypes = {
buttonColor: string,
menuWidth: string,
menuLabel: string,
useAutoComplete: bool,
}
export default OnClickOutside(Dropdown)

View File

@ -17,7 +17,7 @@ const errorsMiddleware = store => next => action => {
const {auth: {me}} = store.getState()
if (action.type === 'ERROR_THROWN') {
const {error: {status, auth}, altText} = action
const {error: {status, auth}, altText, alertType = 'error'} = action
if (status === HTTP_FORBIDDEN) {
const wasSessionTimeout = me !== null
@ -26,7 +26,7 @@ const errorsMiddleware = store => next => action => {
if (wasSessionTimeout) {
store.dispatch(
notify('error', 'Session timed out. Please login again.')
notify(alertType, 'Session timed out. Please login again.')
)
allowNotifications = false
@ -35,9 +35,9 @@ const errorsMiddleware = store => next => action => {
}, notificationsBlackoutDuration)
}
} else if (altText) {
store.dispatch(notify('error', altText))
store.dispatch(notify(alertType, altText))
} else {
store.dispatch(notify('error', 'Cannot communicate with server.'))
store.dispatch(notify(alertType, 'Cannot communicate with server.'))
}
}

View File

@ -12,30 +12,16 @@ $dropdown-menu-max-height: 270px;
Generic width modifiers
Use instead of creating new classes if possible
*/
.dropdown .dropdown-toggle {
.dropdown .dropdown-toggle,
.dropdown .dropdown-autocomplete {
width: 120px; /* Default width */
}
.dropdown {
&-80 .dropdown-toggle {width: 80px;}
&-90 .dropdown-toggle {width: 90px;}
&-100 .dropdown-toggle {width: 100px;}
&-110 .dropdown-toggle {width: 110px;}
&-120 .dropdown-toggle {width: 120px;}
&-130 .dropdown-toggle {width: 130px;}
&-140 .dropdown-toggle {width: 140px;}
&-150 .dropdown-toggle {width: 150px;}
&-160 .dropdown-toggle {width: 160px;}
&-170 .dropdown-toggle {width: 170px;}
&-180 .dropdown-toggle {width: 180px;}
&-190 .dropdown-toggle {width: 190px;}
&-200 .dropdown-toggle {width: 200px;}
&-210 .dropdown-toggle {width: 210px;}
&-220 .dropdown-toggle {width: 220px;}
&-230 .dropdown-toggle {width: 230px;}
&-240 .dropdown-toggle {width: 240px;}
&-250 .dropdown-toggle {width: 250px;}
@for $i from 8 through 30 {
&-#{$i * 10} .dropdown-toggle,
&-#{$i * 10} .dropdown-autocomplete { width: #{$i * 10}px; }
}
}
.dropdown-toggle {
position: relative;
text-align: left;
@ -65,7 +51,54 @@ $dropdown-menu-max-height: 270px;
.dropdown .dropdown-toggle.btn-xs {
height: 22px !important;
line-height: 22px !important;
padding: 0 9px !important;
padding: 0 9px;
}
/*
AutoComplete Field
----------------------------------------------
*/
.dropdown-autocomplete {
position: relative;
padding: 0 !important;
&.btn-xs {height: 22px;}
&.btn-sm {height: 30px;}
&.btn-md {height: 36px;}
&.btn-lg {height: 50px;}
}
.dropdown-autocomplete--input {
position: absolute;
width: 100%;
height: 100%;
outline: none;
background-color: transparent;
border: 0;
color: $g20-white;
padding: 0;
font-weight: 500;
.btn-xs & {padding: 0 18px 0 9px; font-size: 12px;}
.btn-sm & {padding: 0 18px 0 9px; font-size: 13px;}
.btn-md & {padding: 0 34px 0 17px; font-size: 14px;}
.btn-lg & {padding: 0 48px 0 24px; font-size: 18px;}
&::-webkit-input-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&::-moz-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:-ms-input-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:-moz-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:focus {
color: $g20-white;
}
}
.dropdown-empty {
padding: 7px 9px;
font-size: 13px;
color: rgba(255,255,255,0.4);
font-weight: 500;
line-height: 15px;
@include no-user-select();
}
/*
@ -86,6 +119,9 @@ $dropdown-menu-max-height: 270px;
position: relative;
width: 100%;
&.active {
@include gradient-h($c-sapphire, $c-pool);
}
&:hover {
@include gradient-h($c-laser, $c-pool);
}
@ -99,7 +135,7 @@ $dropdown-menu-max-height: 270px;
padding: 7px 9px;
font-size: 13px;
line-height: 15px;
font-weight: 500;
font-weight: 500 !important;
color: $c-yeti !important;
background-color: transparent;
transition:
@ -118,6 +154,16 @@ $dropdown-menu-max-height: 270px;
@include gradient-h($c-ocean, $c-pool);
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-pool);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
.dropdown.dropdown-kapacitor .dropdown-toggle {
color: $c-rainforest !important;
@ -137,6 +183,16 @@ $dropdown-menu-max-height: 270px;
color: $g20-white !important;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-rainforest);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
.dropdown.dropdown-chronograf .dropdown-menu {
@include custom-scrollbar($c-comet, $c-potassium);
@ -151,8 +207,36 @@ $dropdown-menu-max-height: 270px;
color: $g20-white !important;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-comet);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
/*
Dropdown Menu (only js highlighting, works with autocomplete feature)
----------------------------------------------
*/
.dropdown-menu.dropdown-menu--no-highlight {
li.dropdown-item {
&:hover {
background: none;
background-color: transparent;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-pool);
}
}
}
/*
Dropdown Header
----------------------------------------------
@ -256,4 +340,4 @@ $dropdown-menu-max-height: 270px;
background-color: $g2-kevlar !important;
border-color: $c-pool !important;
box-shadow: 0 0 6px 0px $c-pool !important
}
}

View File

@ -80,10 +80,13 @@ $template-control-dropdown-min-width: 118px;
}
.dropdown-menu {
@include gradient-h($c-star,$c-pool);
@include custom-scrollbar-round($c-pool,$c-laser);
li.dropdown-item {
&:hover {@include gradient-h($c-comet,$c-pool);}
&, &:hover {
background: none;
background-color: transparent;
}
&.active {@include gradient-h($c-amethyst,$c-pool);}
}
li.dropdown-item > a {
&, &:focus {background: none;}
@ -91,6 +94,14 @@ $template-control-dropdown-min-width: 118px;
font-size: 12px;
font-family: $code-font;
}
li.dropdown-item.highlight {
&, &:hover {@include gradient-h($c-comet,$c-pool);}
> a {
color: $g20-white;
background: none;
background-color: transparent;
}
}
}
}
.template-control--label {