mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into develop
This commit is contained in:
commit
a9e3130ef6
CHANGELOG.mdREADME.md
docs/sources
package.jsonpkg
public
app
core
features
dashboard
panel
plugins
plugins
datasource
cloudwatch
elasticsearch
graphite
influxdb
mysql
opentsdb
prometheus
panel
sass
test/core/utils
views
@ -9,6 +9,10 @@
|
||||
|
||||
# 4.5.0 (unreleased)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* **Shortcuts**: Adds shortcut for creating new dashboard [#8876](https://github.com/grafana/grafana/pull/8876) thx [@mtanda](https://github.com/mtanda)
|
||||
|
||||
# 4.5.0-beta1 (2017-09-05)
|
||||
|
||||
## New Features
|
||||
|
91
README.md
91
README.md
@ -9,65 +9,8 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
|
||||
|
||||

|
||||
|
||||
- [Install instructions](http://docs.grafana.org/installation/)
|
||||
- [What's New in Grafana 2.0](http://docs.grafana.org/guides/whats-new-in-v2/)
|
||||
- [What's New in Grafana 2.1](http://docs.grafana.org/guides/whats-new-in-v2-1/)
|
||||
- [What's New in Grafana 2.5](http://docs.grafana.org/guides/whats-new-in-v2-5/)
|
||||
- [What's New in Grafana 3.0](http://docs.grafana.org/guides/whats-new-in-v3/)
|
||||
- [What's New in Grafana 4.0](http://docs.grafana.org/guides/whats-new-in-v4/)
|
||||
- [What's New in Grafana 4.1](http://docs.grafana.org/guides/whats-new-in-v4-1/)
|
||||
- [What's New in Grafana 4.2](http://docs.grafana.org/guides/whats-new-in-v4-2/)
|
||||
- [What's New in Grafana 4.3](http://docs.grafana.org/guides/whats-new-in-v4-3/)
|
||||
- [What's New in Grafana 4.4](http://docs.grafana.org/guides/whats-new-in-v4-4/)
|
||||
|
||||
## Features
|
||||
|
||||
### Graphing
|
||||
- Fast rendering, even over large timespans
|
||||
- Click and drag to zoom
|
||||
- Multiple Y-axis, logarithmic scales
|
||||
- Bars, Lines, Points
|
||||
- Smart Y-axis formatting
|
||||
- Series toggles & color selector
|
||||
- Legend values, and formatting options
|
||||
- Grid thresholds, axis labels
|
||||
- [Annotations](http://docs.grafana.org/reference/annotations/)
|
||||
- Any panel can be rendered to PNG (server side using phantomjs)
|
||||
|
||||
### Dashboards
|
||||
- Create, edit, save & search dashboards
|
||||
- Change column spans and row heights
|
||||
- Drag and drop panels to rearrange
|
||||
- [Templating](http://docs.grafana.org/reference/templating/)
|
||||
- [Scripted dashboards](http://docs.grafana.org/reference/scripting/)
|
||||
- [Dashboard playlists](http://docs.grafana.org/reference/playlist/)
|
||||
- [Time range controls](http://docs.grafana.org/reference/timerange/)
|
||||
- [Share snapshots publicly](http://docs.grafana.org/v2.0/reference/sharing/)
|
||||
|
||||
### InfluxDB
|
||||
- Use InfluxDB as a metric data source, annotation source
|
||||
- Query editor with field and tag typeahead, easy group by and function selection
|
||||
|
||||
### Graphite
|
||||
- Graphite target expression parser
|
||||
- Feature rich query composer
|
||||
- Quickly add and edit functions & parameters
|
||||
- Templated queries
|
||||
- [See it in action](http://docs.grafana.org/datasources/graphite/)
|
||||
|
||||
### Elasticsearch, Prometheus & OpenTSDB
|
||||
- Feature rich query editor UI
|
||||
|
||||
### Alerting
|
||||
- Define alert rules using graphs & query conditions
|
||||
- Schedule & evalute alert rules, send notifications to Slack, Hipchat, Email, PagerDuty, etc.
|
||||
|
||||
## Requirements
|
||||
There are no dependencies except an external time series data store. For dashboards and user accounts Grafana can use an embedded
|
||||
database (sqlite3) or you can use an external SQL data base like MySQL or Postgres.
|
||||
|
||||
## Installation
|
||||
Head to [grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get)
|
||||
Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get)
|
||||
the latest release.
|
||||
|
||||
If you have any problems please read the [troubleshooting guide](http://docs.grafana.org/installation/troubleshooting/).
|
||||
@ -84,27 +27,10 @@ the latest master builds [here](https://grafana.com/grafana/download)
|
||||
- Go 1.8.1
|
||||
- NodeJS LTS
|
||||
|
||||
### Get Code
|
||||
|
||||
```bash
|
||||
go get github.com/grafana/grafana
|
||||
```
|
||||
|
||||
Since imports of dependencies use the absolute path `github.com/grafana/grafana` within the `$GOPATH`,
|
||||
you will need to put your version of the code in `$GOPATH/src/github.com/grafana/grafana` to be able
|
||||
to develop and build grafana on a cloned repository. To do so, you can clone your forked repository
|
||||
directly to `$GOPATH/src/github.com/grafana` or you can create a symbolic link from your version
|
||||
of the code to `$GOPATH/src/github.com/grafana/grafana`. The last options makes it possible to change
|
||||
easily the grafana repository you want to build.
|
||||
```bash
|
||||
go get github.com/*your_account*/grafana
|
||||
mkdir $GOPATH/src/github.com/grafana
|
||||
ln -s $GOPATH/src/github.com/*your_account*/grafana $GOPATH/src/github.com/grafana/grafana
|
||||
```
|
||||
|
||||
### Building the backend
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/grafana/grafana
|
||||
go get github.com/grafana/grafana
|
||||
cd ~/go/src/github.com/grafana/grafana
|
||||
go run build.go setup
|
||||
go run build.go build
|
||||
```
|
||||
@ -123,8 +49,7 @@ npm run build
|
||||
To build the frontend assets only on changes:
|
||||
|
||||
```bash
|
||||
sudo npm install -g grunt-cli # to do only once to install grunt command line interface
|
||||
grunt && grunt watch
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Recompile backend on source change
|
||||
@ -134,11 +59,6 @@ go get github.com/Unknwon/bra
|
||||
bra run
|
||||
```
|
||||
|
||||
### Running
|
||||
```bash
|
||||
./bin/grafana-server
|
||||
```
|
||||
|
||||
Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).
|
||||
|
||||
### Dev config
|
||||
@ -149,9 +69,6 @@ You only need to add the options you want to override. Config files are applied
|
||||
1. grafana.ini
|
||||
1. custom.ini
|
||||
|
||||
## Create a pull request
|
||||
Before or after you create a pull request, sign the [contributor license agreement](http://docs.grafana.org/project/cla/).
|
||||
|
||||
## Contribute
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue.
|
||||
And if you have time clone this repo and submit a pull request and help me make Grafana
|
||||
|
@ -13,6 +13,7 @@ Here you can find links to older versions of the documentation that might be bet
|
||||
of Grafana.
|
||||
|
||||
- [Latest](http://docs.grafana.org)
|
||||
- [Version 4.4](http://docs.grafana.org/v4.4)
|
||||
- [Version 4.3](http://docs.grafana.org/v4.3)
|
||||
- [Version 4.2](http://docs.grafana.org/v4.2)
|
||||
- [Version 4.1](http://docs.grafana.org/v4.1)
|
||||
|
@ -41,7 +41,9 @@ Proxy access means that the Grafana backend will proxy all requests from the bro
|
||||
Click the ``Select metric`` link to start navigating the metric space. One you start you can continue using the mouse
|
||||
or keyboard arrow keys. You can select a wildcard and still continue.
|
||||
|
||||

|
||||
{{< docs-imagebox img="/img/docs/v45/graphite_query1_still.png" class="docs-image--center"
|
||||
animated-gif="/img/docs/v45/graphite_query1.gif" >}}
|
||||
|
||||
|
||||
### Functions
|
||||
|
||||
@ -50,13 +52,17 @@ a function is selected it will be added and your focus will be in the text box o
|
||||
a parameter just click on it and it will turn into a text box. To delete a function click the function name followed
|
||||
by the x icon.
|
||||
|
||||

|
||||
{{< docs-imagebox img="/img/docs/v45/graphite_query2_still.png" class="docs-image--center"
|
||||
animated-gif="/img/docs/v45/graphite_query2.gif" >}}
|
||||
|
||||
|
||||
### Optional parameters
|
||||
|
||||
Some functions like aliasByNode support an optional second argument. To add this parameter specify for example 3,-2 as the first parameter and the function editor will adapt and move the -2 to a second parameter. To remove the second optional parameter just click on it and leave it blank and the editor will remove it.
|
||||
|
||||

|
||||
{{< docs-imagebox img="/img/docs/v45/graphite_query3_still.png" class="docs-image--center"
|
||||
animated-gif="/img/docs/v45/graphite_query3.gif" >}}
|
||||
|
||||
|
||||
### Nested Queries
|
||||
|
||||
|
@ -41,7 +41,9 @@ mode is also more secure as the username & password will never reach the browser
|
||||
|
||||
## Query Editor
|
||||
|
||||

|
||||
{{< docs-imagebox img="/img/docs/v45/influxdb_query_still.png" class="docs-image--center"
|
||||
animated-gif="/img/docs/v45/influxdb_query.gif" >}}
|
||||
|
||||
|
||||
You find the InfluxDB editor in the metrics tab in Graph or Singlestat panel's edit mode. You enter edit mode by clicking the
|
||||
panel title, then edit. The editor allows you to select metrics and tags.
|
||||
|
@ -50,15 +50,11 @@ populate the template variable to a desired value from the link.
|
||||
The metrics tab defines what series data and sources to render. Each datasource provides different
|
||||
options.
|
||||
|
||||
## Axes & Grid
|
||||
## Axes
|
||||
|
||||

|
||||
|
||||
The Axes & Grid tab controls the display of axes, grids and legend.
|
||||
|
||||
### Axes
|
||||
|
||||
The ``Left Y`` and ``Right Y`` can be customized using:
|
||||
The Axes tab controls the display of axes, grids and legend. The ``Left Y`` and ``Right Y`` can be customized using:
|
||||
|
||||
- ``Unit`` - The display unit for the Y value
|
||||
- ``Grid Max`` - The maximum Y value. (default auto)
|
||||
|
@ -24,9 +24,9 @@ Read the [Basic Concepts](/guides/basic_concepts) document to get a crash course
|
||||
|
||||
### Top header
|
||||
|
||||
Let's start with creating a new Dashboard. You can find the new Dashboard link at the bottom of the Dashboard picker. You now have a blank Dashboard.
|
||||
Let's start with creating a new Dashboard. You can find the new Dashboard link on the right side of the Dashboard picker. You now have a blank Dashboard.
|
||||
|
||||
<img class="no-shadow" src="/img/docs/v2/v2_top_nav_annotated.png">
|
||||
<img class="no-shadow" src="/img/docs/v45/top_nav_annotated.png">
|
||||
|
||||
The image above shows you the top header for a Dashboard.
|
||||
|
||||
@ -41,19 +41,7 @@ The image above shows you the top header for a Dashboard.
|
||||
|
||||
Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows. Grafana ships with a variety of Panels. Grafana makes it easy to construct the right queries, and customize the display properties so that you can create the perfect Dashboard for your need. Each Panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, Prometheus and Cloudwatch). The [Basic Concepts](/guides/basic_concepts) guide explores these key ideas in detail.
|
||||
|
||||
|
||||
## Adding & Editing Graphs and Panels
|
||||
|
||||

|
||||
|
||||
1. You add panels via row menu. The row menu is the green icon to the left of each row.
|
||||
2. To edit the graph you click on the graph title to open the panel menu, then `Edit`.
|
||||
3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source.
|
||||
|
||||
When you click the `Metrics` tab, you are presented with a Query Editor that is specific to the Panel Data Source. Use the Query Editor to build your queries and Grafana will visualize them in real time.
|
||||
|
||||
|
||||
<img src="/img/docs/v2/dashboard_annotated.png" class="no-shadow">
|
||||
<img src="/img/docs/v45/dashboard_annotated.png" class="no-shadow">
|
||||
|
||||
1. Zoom out time range
|
||||
2. Time picker dropdown. Here you can access relative time range options, auto refresh options and set custom absolute time ranges.
|
||||
@ -62,6 +50,17 @@ When you click the `Metrics` tab, you are presented with a Query Editor that is
|
||||
5. Dashboard panel. You edit panels by clicking the panel title.
|
||||
6. Graph legend. You can change series colors, y-axis and series visibility directly from the legend.
|
||||
|
||||
|
||||
## Adding & Editing Graphs and Panels
|
||||
|
||||

|
||||
|
||||
1. You add panels via row menu. The row menu is the icon to the left of each row.
|
||||
2. To edit the graph you click on the graph title to open the panel menu, then `Edit`.
|
||||
3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source.
|
||||
|
||||
When you click the `Metrics` tab, you are presented with a Query Editor that is specific to the Panel Data Source. Use the Query Editor to build your queries and Grafana will visualize them in real time.
|
||||
|
||||
## Drag-and-Drop panels
|
||||
|
||||
You can Drag-and-Drop Panels within and between Rows. Click and hold the Panel title, and drag it to its new location. You can also easily resize panels by clicking the (-) and (+) icons.
|
||||
|
@ -31,6 +31,11 @@ There is also integrated function docs right from the query editor!
|
||||
Create column styles that turn cells into links that use the value in the cell (or other other row values) to generate a url to another dashboard or system:
|
||||

|
||||
|
||||
### Query Inspector
|
||||
Query Inspector is a new feature that shows query requests and responses. This can be helpful if a graph is not shown or shows something very different than what you expected.
|
||||
More information [here](https://community.grafana.com/t/using-grafanas-query-inspector-to-troubleshoot-issues/2630).
|
||||

|
||||
|
||||
## Changelog
|
||||
|
||||
### New Features
|
||||
|
@ -137,7 +137,7 @@ parent = "http_api"
|
||||
|
||||
`POST /api/datasources`
|
||||
|
||||
**Example Request**:
|
||||
**Example Graphite Request**:
|
||||
|
||||
POST /api/datasources HTTP/1.1
|
||||
Accept: application/json
|
||||
@ -152,6 +152,28 @@ parent = "http_api"
|
||||
"basicAuth":false
|
||||
}
|
||||
|
||||
**Example CloudWatch Request**:
|
||||
```
|
||||
POST /api/datasources HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name": "test_datasource",
|
||||
"type": "cloudwatch",
|
||||
"url": "http://monitoring.us-west-1.amazonaws.com",
|
||||
"access": "proxy",
|
||||
"jsonData": {
|
||||
"authType": "keys",
|
||||
"defaultRegion": "us-west-1"
|
||||
},
|
||||
"secureJsonData": {
|
||||
"accessKey": "Ol4pIDpeKSA6XikgOl4p",
|
||||
"secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
|
@ -11,13 +11,18 @@ weight = 8
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## visualization & query issues
|
||||
## Visualization & Query issues
|
||||
|
||||
{{< imgbox max-width="40%" img="/img/docs/v45/query_inspector.png" caption="Query Inspector" >}}
|
||||
|
||||
The most common problems are related to the query & response from you data source. Even if it looks
|
||||
like a bug or visualization issue in Grafana it is 99% of time a problem with the data source query or
|
||||
the data source response.
|
||||
|
||||
So make sure to check the query sent and the raw response, learn how in this guide: [How to troubleshoot metric query issues](https://community.grafana.com/t/how-to-troubleshoot-metric-query-issues/50)
|
||||
To check this you should use Query Inspector (new in Grafana v4.5). The query Inspector shows query requests and responses.
|
||||
|
||||
For more on the query insector read [this guide here](https://community.grafana.com/t/using-grafanas-query-inspector-to-troubleshoot-issues/2630). For
|
||||
older versions of Grafana read the [how troubleshoot metric query issue](https://community.grafana.com/t/how-to-troubleshoot-metric-query-issues/50/2) article.
|
||||
|
||||
## Logging
|
||||
|
||||
|
@ -27,7 +27,7 @@ this folder to anywhere you want Grafana to run from. Go into the
|
||||
|
||||
The default Grafana port is `3000`, this port requires extra permissions
|
||||
on windows. Edit `custom.ini` and uncomment the `http_port`
|
||||
configuration option and change it to something like `8080` or similar.
|
||||
configuration option (`;` is the comment character in ini files) and change it to something like `8080` or similar.
|
||||
That port should not require extra Windows privileges.
|
||||
|
||||
Start Grafana by executing `grafana-server.exe`, preferably from the
|
||||
|
@ -43,3 +43,25 @@ Playlists can also be manually controlled utilizing the Playlist controls at the
|
||||
Click the stop button to stop the Playlist, and exit to the current Dashboard.
|
||||
Click the next button to advance to the next Dashboard in the Playlist.
|
||||
Click the back button to rewind to the previous Dashboard in the Playlist.
|
||||
|
||||
## TV or Kiosk Mode
|
||||
|
||||
In TV mode the top navbar, row & panel controls will all fade to transparent.
|
||||
|
||||
This happens automatically after one minute of user inactivity but can also be toggled manually
|
||||
with the `d v` sequence shortcut. Any mouse movement or keyboard action will
|
||||
restore navbar & controls.
|
||||
|
||||
Another feature is the kiosk mode - in kiosk mode the navbar is completely hidden/removed from view. This can be enabled with the `d k`
|
||||
shortcut.
|
||||
|
||||
To put a playlist into kiosk mode, use the `d k` shortcut after the playlist has started. The same shortcut will toggle the playlist out of kiosk mode.
|
||||
|
||||
### Linking to the Playlist in Kiosk Mode
|
||||
|
||||
If you want to create a link to the playlist with kiosk mode enabled:
|
||||
|
||||
1. Copy the Start Url (by right clicking on the Play button and choosing Copy link address).
|
||||
2. Add the `?kiosk` parameter to the url.
|
||||
|
||||
For example, to open the first playlist on the Grafana Play site in kiosk mode: [http://play.grafana.org/playlists/play/1?kiosk](http://play.grafana.org/playlists/play/1?kiosk)
|
||||
|
@ -53,13 +53,10 @@
|
||||
"systemjs": "0.19.41",
|
||||
"zone.js": "^0.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x",
|
||||
"npm": "2.14.x"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./node_modules/grunt-cli/bin/grunt",
|
||||
"test": "./node_modules/grunt-cli/bin/grunt test"
|
||||
"test": "./node_modules/grunt-cli/bin/grunt test",
|
||||
"dev": "./node_modules/grunt-cli/bin/grunt && ./node_modules/grunt-cli/bin/grunt watch"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
@ -78,6 +78,7 @@ func init() {
|
||||
"AWS/Lambda": {"Invocations", "Errors", "Duration", "Throttles", "IteratorAge"},
|
||||
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
|
||||
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
||||
"AWS/NATGateway": {"PacketsOutToDestination", "PacketsOutToSource", "PacketsInFromSource", "PacketsInFromDestination", "BytesOutToDestination", "BytesOutToSource", "BytesInFromSource", "BytesInFromDestination", "ErrorPortAllocation", "ActiveConnectionCount", "ConnectionAttemptCount", "ConnectionEstablishedCount", "IdleTimeoutCount", "PacketsDropCount"},
|
||||
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
|
||||
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "ReadIOPS", "ReadLatency", "ReadThroughput", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
@ -122,6 +123,7 @@ func init() {
|
||||
"AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"},
|
||||
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||
"AWS/NATGateway": {"NatGatewayId"},
|
||||
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
||||
"AWS/Redshift": {"NodeID", "ClusterIdentifier"},
|
||||
"AWS/RDS": {"DBInstanceIdentifier", "DBClusterIdentifier", "DatabaseClass", "EngineName", "Role"},
|
||||
|
@ -29,7 +29,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response {
|
||||
return ApiError(400, "Query missing datasourceId", nil)
|
||||
}
|
||||
|
||||
dsQuery := models.GetDataSourceByIdQuery{Id: dsId}
|
||||
dsQuery := models.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&dsQuery); err != nil {
|
||||
return ApiError(500, "failed to fetch data source", err)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gocontext "context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@ -112,6 +114,10 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
|
||||
resp, err := c.HandleRequest(context.Ctx, req)
|
||||
if err != nil {
|
||||
if err == gocontext.DeadlineExceeded {
|
||||
return nil, fmt.Errorf("Alert execution exceeded the timeout")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,13 @@ func Decrypt(payload []byte, secret string) ([]byte, error) {
|
||||
}
|
||||
iv := payload[saltLength : saltLength+aes.BlockSize]
|
||||
payload = payload[saltLength+aes.BlockSize:]
|
||||
payloadDst := make([]byte, len(payload))
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
|
||||
// XORKeyStream can work in-place if the two arguments are the same.
|
||||
stream.XORKeyStream(payload, payload)
|
||||
return payload, nil
|
||||
stream.XORKeyStream(payloadDst, payload)
|
||||
return payloadDst, nil
|
||||
}
|
||||
|
||||
func Encrypt(payload []byte, secret string) ([]byte, error) {
|
||||
|
@ -40,11 +40,11 @@ const DEFAULT_MAX_LINES = 10;
|
||||
const DEFAULT_TAB_SIZE = 2;
|
||||
const DEFAULT_BEHAVIOURS = true;
|
||||
|
||||
const GRAFANA_MODULES = ['mode-prometheus', 'snippets-prometheus', 'theme-grafana-dark'];
|
||||
const GRAFANA_MODULES = ['theme-grafana-dark'];
|
||||
const GRAFANA_MODULE_BASE = "public/app/core/components/code_editor/";
|
||||
|
||||
// Trick for loading additional modules
|
||||
function setModuleUrl(moduleType, name) {
|
||||
function setModuleUrl(moduleType, name, pluginBaseUrl = null) {
|
||||
let baseUrl = ACE_SRC_BASE;
|
||||
let aceModeName = `ace/${moduleType}/${name}`;
|
||||
let moduleName = `${moduleType}-${name}`;
|
||||
@ -54,6 +54,10 @@ function setModuleUrl(moduleType, name) {
|
||||
baseUrl = GRAFANA_MODULE_BASE;
|
||||
}
|
||||
|
||||
if (pluginBaseUrl) {
|
||||
baseUrl = pluginBaseUrl + '/';
|
||||
}
|
||||
|
||||
if (moduleType === 'snippets') {
|
||||
componentName = `${moduleType}/${name}.js`;
|
||||
}
|
||||
@ -111,6 +115,17 @@ function link(scope, elem, attrs) {
|
||||
let textarea = elem.find("textarea");
|
||||
textarea.addClass('gf-form-input');
|
||||
|
||||
if (scope.codeEditorFocus) {
|
||||
setTimeout(function () {
|
||||
textarea.focus();
|
||||
var domEl = textarea[0];
|
||||
if (domEl.setSelectionRange) {
|
||||
var pos = textarea.val().length * 2;
|
||||
domEl.setSelectionRange(pos, pos);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
editorSession.on('change', (e) => {
|
||||
scope.$apply(() => {
|
||||
@ -148,8 +163,8 @@ function link(scope, elem, attrs) {
|
||||
|
||||
function setLangMode(lang) {
|
||||
let aceModeName = `ace/mode/${lang}`;
|
||||
setModuleUrl("mode", lang);
|
||||
setModuleUrl("snippets", lang);
|
||||
setModuleUrl("mode", lang, scope.datasource.meta.baseUrl || null);
|
||||
setModuleUrl("snippets", lang, scope.datasource.meta.baseUrl || null);
|
||||
editorSession.setMode(aceModeName);
|
||||
|
||||
ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
|
||||
@ -199,6 +214,8 @@ export function codeEditorDirective() {
|
||||
template: editorTemplate,
|
||||
scope: {
|
||||
content: "=",
|
||||
datasource: "=",
|
||||
codeEditorFocus: "<",
|
||||
onChange: "&",
|
||||
getCompleter: "&"
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ export class AlertSrv {
|
||||
|
||||
init() {
|
||||
this.$rootScope.onAppEvent('alert-error', (e, alert) => {
|
||||
this.set(alert[0], alert[1], 'error', 7000);
|
||||
this.set(alert[0], alert[1], 'error', 12000);
|
||||
}, this.$rootScope);
|
||||
|
||||
this.$rootScope.onAppEvent('alert-warning', (e, alert) => {
|
||||
@ -33,6 +33,14 @@ export class AlertSrv {
|
||||
appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
|
||||
}
|
||||
|
||||
getIconForSeverity(severity) {
|
||||
switch (severity) {
|
||||
case 'success': return 'fa fa-check';
|
||||
case 'error': return 'fa fa-exclamation-triangle';
|
||||
default: return 'fa fa-exclamation';
|
||||
}
|
||||
}
|
||||
|
||||
set(title, text, severity, timeout) {
|
||||
if (_.isObject(text)) {
|
||||
console.log('alert error', text);
|
||||
@ -45,6 +53,7 @@ export class AlertSrv {
|
||||
title: title || '',
|
||||
text: text || '',
|
||||
severity: severity || 'info',
|
||||
icon: this.getIconForSeverity(severity)
|
||||
};
|
||||
|
||||
var newAlertJson = angular.toJson(newAlert);
|
||||
|
@ -64,7 +64,13 @@ export class BackendSrv {
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
this.alertSrv.set("Problem!", data.message, data.severity, 10000);
|
||||
let description = "";
|
||||
let message = data.message;
|
||||
if (message.length > 80) {
|
||||
description = message;
|
||||
message = "Error";
|
||||
}
|
||||
this.alertSrv.set(message, description, data.severity, 10000);
|
||||
}
|
||||
|
||||
throw data;
|
||||
@ -97,7 +103,7 @@ export class BackendSrv {
|
||||
return results.data;
|
||||
}, err => {
|
||||
// handle unauthorized
|
||||
if (err.status === 401 && firstAttempt) {
|
||||
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
|
||||
return this.loginPing().then(() => {
|
||||
options.retry = 1;
|
||||
return this.request(options);
|
||||
|
@ -193,6 +193,10 @@ export class KeybindingSrv {
|
||||
}
|
||||
});
|
||||
|
||||
this.bind('d n', e => {
|
||||
this.$location.url("/dashboard/new");
|
||||
});
|
||||
|
||||
this.bind('d r', () => {
|
||||
scope.broadcastRefresh();
|
||||
});
|
||||
|
@ -17,90 +17,87 @@ function($, _) {
|
||||
kbn.round_interval = function(interval) {
|
||||
switch (true) {
|
||||
// 0.015s
|
||||
case (interval <= 15):
|
||||
case (interval < 15):
|
||||
return 10; // 0.01s
|
||||
// 0.035s
|
||||
case (interval <= 35):
|
||||
case (interval < 35):
|
||||
return 20; // 0.02s
|
||||
// 0.075s
|
||||
case (interval <= 75):
|
||||
case (interval < 75):
|
||||
return 50; // 0.05s
|
||||
// 0.15s
|
||||
case (interval <= 150):
|
||||
case (interval < 150):
|
||||
return 100; // 0.1s
|
||||
// 0.35s
|
||||
case (interval <= 350):
|
||||
case (interval < 350):
|
||||
return 200; // 0.2s
|
||||
// 0.75s
|
||||
case (interval <= 750):
|
||||
case (interval < 750):
|
||||
return 500; // 0.5s
|
||||
// 1.5s
|
||||
case (interval <= 1500):
|
||||
case (interval < 1500):
|
||||
return 1000; // 1s
|
||||
// 3.5s
|
||||
case (interval <= 3500):
|
||||
case (interval < 3500):
|
||||
return 2000; // 2s
|
||||
// 7.5s
|
||||
case (interval <= 7500):
|
||||
case (interval < 7500):
|
||||
return 5000; // 5s
|
||||
// 12.5s
|
||||
case (interval <= 12500):
|
||||
case (interval < 12500):
|
||||
return 10000; // 10s
|
||||
// 17.5s
|
||||
case (interval <= 17500):
|
||||
case (interval < 17500):
|
||||
return 15000; // 15s
|
||||
// 25s
|
||||
case (interval <= 25000):
|
||||
case (interval < 25000):
|
||||
return 20000; // 20s
|
||||
// 45s
|
||||
case (interval <= 45000):
|
||||
case (interval < 45000):
|
||||
return 30000; // 30s
|
||||
// 1.5m
|
||||
case (interval <= 90000):
|
||||
case (interval < 90000):
|
||||
return 60000; // 1m
|
||||
// 3.5m
|
||||
case (interval <= 210000):
|
||||
case (interval < 210000):
|
||||
return 120000; // 2m
|
||||
// 7.5m
|
||||
case (interval <= 450000):
|
||||
case (interval < 450000):
|
||||
return 300000; // 5m
|
||||
// 12.5m
|
||||
case (interval <= 750000):
|
||||
case (interval < 750000):
|
||||
return 600000; // 10m
|
||||
// 12.5m
|
||||
case (interval <= 1050000):
|
||||
case (interval < 1050000):
|
||||
return 900000; // 15m
|
||||
// 25m
|
||||
case (interval <= 1500000):
|
||||
case (interval < 1500000):
|
||||
return 1200000; // 20m
|
||||
// 45m
|
||||
case (interval <= 2700000):
|
||||
case (interval < 2700000):
|
||||
return 1800000; // 30m
|
||||
// 1.5h
|
||||
case (interval <= 5400000):
|
||||
case (interval < 5400000):
|
||||
return 3600000; // 1h
|
||||
// 2.5h
|
||||
case (interval <= 9000000):
|
||||
case (interval < 9000000):
|
||||
return 7200000; // 2h
|
||||
// 4.5h
|
||||
case (interval <= 16200000):
|
||||
case (interval < 16200000):
|
||||
return 10800000; // 3h
|
||||
// 9h
|
||||
case (interval <= 32400000):
|
||||
case (interval < 32400000):
|
||||
return 21600000; // 6h
|
||||
// 24h
|
||||
case (interval <= 86400000):
|
||||
// 1d
|
||||
case (interval < 86400000):
|
||||
return 43200000; // 12h
|
||||
// 48h
|
||||
case (interval <= 172800000):
|
||||
return 86400000; // 24h
|
||||
// 1w
|
||||
case (interval <= 604800000):
|
||||
return 86400000; // 24h
|
||||
case (interval < 604800000):
|
||||
return 86400000; // 1d
|
||||
// 3w
|
||||
case (interval <= 1814400000):
|
||||
case (interval < 1814400000):
|
||||
return 604800000; // 1w
|
||||
// 2y
|
||||
// 6w
|
||||
case (interval < 3628800000):
|
||||
return 2592000000; // 30d
|
||||
default:
|
||||
@ -134,7 +131,7 @@ function($, _) {
|
||||
return nummilliseconds + 'ms';
|
||||
}
|
||||
|
||||
return 'less then a millisecond'; //'just now' //or other string you like;
|
||||
return 'less than a millisecond'; //'just now' //or other string you like;
|
||||
};
|
||||
|
||||
kbn.to_percent = function(number,outof) {
|
||||
|
@ -83,7 +83,7 @@ export class DashboardSrv {
|
||||
}
|
||||
|
||||
this.$rootScope.appEvent('dashboard-saved', this.dash);
|
||||
this.$rootScope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]);
|
||||
this.$rootScope.appEvent('alert-success', ['Dashboard saved']);
|
||||
}
|
||||
|
||||
save(clone, options) {
|
||||
|
@ -116,16 +116,14 @@ class TimeSrv {
|
||||
|
||||
setAutoRefresh(interval) {
|
||||
this.dashboard.refresh = interval;
|
||||
this.cancelNextRefresh();
|
||||
if (interval) {
|
||||
var intervalMs = kbn.interval_to_ms(interval);
|
||||
|
||||
this.$timeout(() => {
|
||||
this.refreshTimer = this.timer.register(this.$timeout(() => {
|
||||
this.startNextRefreshTimer(intervalMs);
|
||||
this.refreshDashboard();
|
||||
}, intervalMs);
|
||||
|
||||
} else {
|
||||
this.cancelNextRefresh();
|
||||
}, intervalMs));
|
||||
}
|
||||
|
||||
// update url
|
||||
|
@ -9,7 +9,7 @@ export class MetricsTabCtrl {
|
||||
panel: any;
|
||||
panelCtrl: any;
|
||||
datasources: any[];
|
||||
current: any;
|
||||
datasourceInstance: any;
|
||||
nextRefId: string;
|
||||
dashboard: DashboardModel;
|
||||
panelDsValue: any;
|
||||
@ -29,23 +29,26 @@ export class MetricsTabCtrl {
|
||||
this.panel = this.panelCtrl.panel;
|
||||
this.dashboard = this.panelCtrl.dashboard;
|
||||
this.datasources = datasourceSrv.getMetricSources();
|
||||
this.panelDsValue = this.panelCtrl.panel.datasource || null;
|
||||
this.panelDsValue = this.panelCtrl.panel.datasource;
|
||||
|
||||
for (let ds of this.datasources) {
|
||||
if (ds.value === this.panelDsValue) {
|
||||
this.current = ds;
|
||||
this.datasourceInstance = ds;
|
||||
}
|
||||
}
|
||||
|
||||
this.addQueryDropdown = {text: 'Add Query', value: null, fake: true};
|
||||
|
||||
// update next ref id
|
||||
this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel);
|
||||
this.updateDatasourceOptions();
|
||||
}
|
||||
|
||||
updateDatasourceOptions() {
|
||||
this.hasQueryHelp = this.current.meta.hasQueryHelp;
|
||||
this.queryOptions = this.current.meta.queryOptions;
|
||||
if (this.datasourceInstance) {
|
||||
this.hasQueryHelp = this.datasourceInstance.meta.hasQueryHelp;
|
||||
this.queryOptions = this.datasourceInstance.meta.queryOptions;
|
||||
}
|
||||
}
|
||||
|
||||
getOptions(includeBuiltin) {
|
||||
@ -61,7 +64,7 @@ export class MetricsTabCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
this.current = option.datasource;
|
||||
this.datasourceInstance = option.datasource;
|
||||
this.panelCtrl.setDatasource(option.datasource);
|
||||
this.updateDatasourceOptions();
|
||||
}
|
||||
@ -85,7 +88,7 @@ export class MetricsTabCtrl {
|
||||
this.queryTroubleshooterOpen = false;
|
||||
this.helpOpen = !this.helpOpen;
|
||||
|
||||
this.backendSrv.get(`/api/plugins/${this.current.meta.id}/markdown/query_help`).then(res => {
|
||||
this.backendSrv.get(`/api/plugins/${this.datasourceInstance.meta.id}/markdown/query_help`).then(res => {
|
||||
var md = new Remarkable();
|
||||
this.helpHtml = this.$sce.trustAsHtml(md.render(res));
|
||||
});
|
||||
|
@ -73,7 +73,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="query-editor-rows gf-form-group">
|
||||
<div class="query-editor-rows gf-form-group" ng-if="ctrl.datasourceInstance">
|
||||
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
|
||||
<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
|
||||
<plugin-component type="query-ctrl">
|
||||
@ -89,8 +89,13 @@
|
||||
</span>
|
||||
<span class="gf-form-query-letter-cell-letter">{{ctrl.panelCtrl.nextRefId}}</span>
|
||||
</label>
|
||||
<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.current.meta.mixed">
|
||||
<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.datasourceInstance.meta.mixed">
|
||||
Add Query
|
||||
</button>
|
||||
<div class="dropdown" ng-if="ctrl.current.meta.mixed">
|
||||
<gf-form-dropdown model="ctrl.addQueryDropdown" get-options="ctrl.getOptions(false)" on-change="ctrl.addMixedQuery($option)">
|
||||
</gf-form-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,9 +10,11 @@ export class QueryCtrl {
|
||||
panel: any;
|
||||
hasRawMode: boolean;
|
||||
error: string;
|
||||
isLastQuery: boolean;
|
||||
|
||||
constructor(public $scope, private $injector) {
|
||||
this.panel = this.panelCtrl.panel;
|
||||
this.isLastQuery = _.indexOf(this.panel.targets, this.target) === (this.panel.targets.length - 1);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
@ -134,21 +134,18 @@ export class DataSourceEditCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
this.testing = {done: false};
|
||||
this.testing = {done: false, status: 'error'};
|
||||
|
||||
// make test call in no backend cache context
|
||||
this.backendSrv.withNoBackendCache(() => {
|
||||
return datasource.testDatasource().then(result => {
|
||||
this.testing.message = result.message;
|
||||
this.testing.status = result.status;
|
||||
this.testing.title = result.title;
|
||||
}).catch(err => {
|
||||
if (err.statusText) {
|
||||
this.testing.message = err.statusText;
|
||||
this.testing.title = "HTTP Error";
|
||||
this.testing.message = 'HTTP Error ' + err.statusText;
|
||||
} else {
|
||||
this.testing.message = err.message;
|
||||
this.testing.title = "Unknown error";
|
||||
}
|
||||
});
|
||||
}).finally(() => {
|
||||
|
@ -59,16 +59,19 @@
|
||||
|
||||
<br />
|
||||
|
||||
<div ng-if="ctrl.testing">
|
||||
<div ng-if="ctrl.testing" class="gf-form-group">
|
||||
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||
<div class="alert-{{ctrl.testing.status}} alert">
|
||||
<div class="alert-title">{{ctrl.testing.title}}</div>
|
||||
<div ng-bind='ctrl.testing.message'></div>
|
||||
<div class="alert-{{ctrl.testing.status}} alert" ng-show="ctrl.testing.done">
|
||||
<div class="alert-icon">
|
||||
<i class="fa fa-exclamation-triangle" ng-show="ctrl.testing.status === 'error'"></i>
|
||||
<i class="fa fa-check" ng-show="ctrl.testing.status !== 'error'"></i>
|
||||
</div>
|
||||
<div class="alert-body">
|
||||
<div class="alert-title">{{ctrl.testing.message}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-success" ng-click="ctrl.saveChanges()">Save</button>
|
||||
<button type="submit" class="btn btn-danger" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
|
||||
@ -76,7 +79,6 @@
|
||||
</button>
|
||||
<a class="btn btn-link" href="datasources">Cancel</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -335,7 +335,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
|
||||
var dimensions = {};
|
||||
|
||||
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
|
||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||
return { status: 'success', message: 'Data source is working' };
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -175,9 +175,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
return this.getFields({type: 'date'}).then(function(dateFields) {
|
||||
var timeField = _.find(dateFields, {text: this.timeField});
|
||||
if (!timeField) {
|
||||
return { status: "error", message: "No date field named " + this.timeField + ' found', title: "Error" };
|
||||
return { status: "error", message: "No date field named " + this.timeField + ' found' };
|
||||
}
|
||||
return { status: "success", message: "Index OK. Time field name OK.", title: "Success" };
|
||||
return { status: "success", message: "Index OK. Time field name OK." };
|
||||
}.bind(this), function(err) {
|
||||
console.log(err);
|
||||
if (err.data && err.data.error) {
|
||||
@ -185,9 +185,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
if (err.data.error.reason) {
|
||||
message = err.data.error.reason;
|
||||
}
|
||||
return { status: "error", message: message, title: "Error" };
|
||||
return { status: "error", message: message };
|
||||
} else {
|
||||
return { status: "error", message: err.status, title: "Error" };
|
||||
return { status: "error", message: err.status };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -205,7 +205,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function () {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
return { status: "success", message: "Data source is working"};
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,11 @@ describe('GraphiteQueryCtrl', function() {
|
||||
ctx.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
|
||||
ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.panelCtrl = {panel: {}};
|
||||
ctx.panelCtrl = {
|
||||
panel: {
|
||||
targets: [ctx.target]
|
||||
}
|
||||
};
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
|
||||
ctx.ctrl = $controller(GraphiteQueryCtrl, {$scope: ctx.scope}, {
|
||||
|
@ -196,11 +196,11 @@ export default class InfluxDatasource {
|
||||
return this.metricFindQuery('SHOW DATABASES').then(res => {
|
||||
let found = _.find(res, {text: this.database});
|
||||
if (!found) {
|
||||
return { status: "error", message: "Could not find the specified database name.", title: "DB Not found" };
|
||||
return { status: "error", message: "Could not find the specified database name." };
|
||||
}
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
return { status: "success", message: "Data source is working" };
|
||||
}).catch(err => {
|
||||
return { status: "error", message: err.message, title: "Test Failed" };
|
||||
return { status: "error", message: err.message };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@
|
||||
<div class="gf-form-inline" ng-if="ctrl.target.orderByTime === 'DESC'">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">ORDER BY</label>
|
||||
<label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span classs="query-keyword">DESC</span> <i class="fa fa-remove"></i></label>
|
||||
<label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span class="query-keyword">DESC</span> <i class="fa fa-remove"></i></label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
|
@ -19,9 +19,13 @@ describe('InfluxDBQueryCtrl', function() {
|
||||
ctx.$q = $q;
|
||||
ctx.scope = $rootScope.$new();
|
||||
ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.panelCtrl = {panel: {}};
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
ctx.target = {target: {}};
|
||||
ctx.panelCtrl = {
|
||||
panel: {
|
||||
targets: [ctx.target]
|
||||
}
|
||||
};
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
ctx.ctrl = $controller(InfluxQueryCtrl, {$scope: ctx.scope}, {
|
||||
panelCtrl: ctx.panelCtrl,
|
||||
target: ctx.target,
|
||||
|
@ -118,13 +118,13 @@ export class MysqlDatasource {
|
||||
}],
|
||||
}
|
||||
}).then(res => {
|
||||
return { status: "success", message: "Database Connection OK", title: "Success" };
|
||||
return { status: "success", message: "Database Connection OK"};
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
if (err.data && err.data.message) {
|
||||
return { status: "error", message: err.data.message, title: "Error" };
|
||||
return { status: "error", message: err.data.message };
|
||||
} else {
|
||||
return { status: "error", message: err.status, title: "Error" };
|
||||
return { status: "error", message: err.status };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ function (angular, _, dateMath) {
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this._performSuggestQuery('cpu', 'metrics').then(function () {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
return { status: "success", message: "Data source is working" };
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,11 @@ describe('OpenTsQueryCtrl', function() {
|
||||
ctx.$q = $q;
|
||||
ctx.scope = $rootScope.$new();
|
||||
ctx.target = {target: ''};
|
||||
ctx.panelCtrl = {panel: {}};
|
||||
ctx.panelCtrl = {
|
||||
panel: {
|
||||
targets: [ctx.target]
|
||||
}
|
||||
};
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
ctx.datasource.getAggregators = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.datasource.getFilterTypes = sinon.stub().returns(ctx.$q.when([]));
|
||||
|
@ -241,7 +241,7 @@ export class PrometheusDatasource {
|
||||
|
||||
testDatasource() {
|
||||
return this.metricFindQuery('metrics(.*)').then(function() {
|
||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||
return { status: 'success', message: 'Data source is working'};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -65,20 +65,20 @@ var PrometheusHighlightRules = function() {
|
||||
regex : "\\s+"
|
||||
} ],
|
||||
"start-label-matcher" : [ {
|
||||
token : "label.name",
|
||||
token : "keyword",
|
||||
regex : '[a-zA-Z_][a-zA-Z0-9_]*'
|
||||
}, {
|
||||
token : "label.matching_operator",
|
||||
regex : '=|!=|=~|!~'
|
||||
token : "keyword.operator",
|
||||
regex : '=~|=|!~|!='
|
||||
}, {
|
||||
token : "label.value",
|
||||
token : "string",
|
||||
regex : '"[^"]*"|\'[^\']*\''
|
||||
}, {
|
||||
token : "label.matching_delimiter",
|
||||
token : "punctuation.operator",
|
||||
regex : ",",
|
||||
push : 'start-label-matcher'
|
||||
}, {
|
||||
token : "label.matching_end",
|
||||
token : "paren.rparen",
|
||||
regex : "}",
|
||||
next : "start"
|
||||
} ]
|
@ -1,8 +1,8 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<code-editor content="ctrl.target.expr" on-change="ctrl.refreshMetricData()"
|
||||
get-completer="ctrl.getCompleter()" data-mode="prometheus">
|
||||
<code-editor content="ctrl.target.expr" datasource="ctrl.datasource" on-change="ctrl.refreshMetricData()"
|
||||
get-completer="ctrl.getCompleter()" data-mode="prometheus" code-editor-focus="ctrl.isLastQuery">
|
||||
</code-editor>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,27 @@
|
||||
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
|
||||
|
||||
import {PromCompleter} from '../completer';
|
||||
import {PrometheusDatasource} from '../datasource';
|
||||
|
||||
describe('Prometheus editor completer', function() {
|
||||
|
||||
let editor = {};
|
||||
let session = {
|
||||
getTokenAt: sinon.stub().returns({}),
|
||||
getLine: sinon.stub().returns(""),
|
||||
};
|
||||
|
||||
let datasourceStub = <PrometheusDatasource>{};
|
||||
let completer = new PromCompleter(datasourceStub);
|
||||
|
||||
describe("When inside brackets", () => {
|
||||
|
||||
it("Should return range vectors", () => {
|
||||
completer.getCompletions(editor, session, 10, "[", (s, res) => {
|
||||
expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -48,26 +48,14 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisOptionChanged()"> </select>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisModeChanged()"> </select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-6">Name</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.name" get-options="ctrl.getDataFieldNames(false)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-6">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" get-options="ctrl.getDataFieldNames(true)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
|
||||
<label class="gf-form-label width-6">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisValueChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Histogram mode -->
|
||||
|
@ -59,10 +59,12 @@ export class AxesEditorCtrl {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
xAxisOptionChanged() {
|
||||
if (!this.panel.xaxis.values || !this.panel.xaxis.values[0]){
|
||||
this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode();
|
||||
}
|
||||
xAxisModeChanged() {
|
||||
this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode();
|
||||
this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
|
||||
}
|
||||
|
||||
xAxisValueChanged() {
|
||||
this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Mode</span>
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown']"></select>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -106,7 +106,7 @@ $tight-form-func-bg: #333;
|
||||
$tight-form-func-highlight-bg: #444;
|
||||
|
||||
$modal-background: $black;
|
||||
$code-tag-bg: $dark-5;
|
||||
$code-tag-bg: $gray-1;
|
||||
$code-tag-border: lighten($code-tag-bg, 2%);
|
||||
|
||||
|
||||
@ -256,17 +256,15 @@ $paginationActiveBackground: $blue;
|
||||
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
$state-warning-text: $warn;
|
||||
$state-warning-bg: $brand-warning;
|
||||
$warning-text-color: $warn;
|
||||
$error-text-color: #E84D4D;
|
||||
$success-text-color: #12D95A;
|
||||
$info-text-color: $blue-dark;
|
||||
|
||||
$errorText: #E84D4D;
|
||||
$errorBackground: $btn-danger-bg;
|
||||
|
||||
$successText: #12D95A;
|
||||
$successBackground: $btn-success-bg;
|
||||
|
||||
$infoText: $blue-dark;
|
||||
$infoBackground: $blue-dark;
|
||||
$alert-error-bg: linear-gradient(90deg, #d44939, #e0603d);
|
||||
$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274);
|
||||
$alert-warning-bg: linear-gradient(90deg, #d44939, #e0603d);
|
||||
$alert-info-bg: linear-gradient(100deg, #1a4552, #00374a);
|
||||
|
||||
// popover
|
||||
$popover-bg: $panel-bg;
|
||||
@ -276,6 +274,8 @@ $popover-border-color: $gray-1;
|
||||
$popover-help-bg: $btn-secondary-bg;
|
||||
$popover-help-color: $text-color;
|
||||
|
||||
$popover-error-bg: $btn-danger-bg;
|
||||
|
||||
// Tooltips and popovers
|
||||
// -------------------------
|
||||
$tooltipColor: $popover-help-color;
|
||||
@ -294,7 +294,7 @@ $card-background-hover: linear-gradient(135deg, #343434, #262626);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
|
||||
|
||||
// info box
|
||||
$info-box-background: linear-gradient(100deg, #1a4552, #0b2127);
|
||||
$info-box-background: linear-gradient(100deg, #1a4552, #00374a);
|
||||
|
||||
// footer
|
||||
$footer-link-color: $gray-1;
|
||||
|
@ -273,29 +273,23 @@ $paginationActiveBackground: $blue;
|
||||
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
$state-warning-text: lighten($orange, 10%);
|
||||
$state-warning-bg: $orange;
|
||||
$warningBorder: transparent;
|
||||
$warning-text-color: lighten($orange, 10%);
|
||||
$error-text-color: lighten($red, 10%);
|
||||
$success-text-color: lighten($green, 10%);
|
||||
$info-text-color: $blue;
|
||||
|
||||
$errorText: lighten($red, 10%);
|
||||
$errorBackground: $red;
|
||||
$errorBorder: transparent;
|
||||
|
||||
$successText: lighten($green, 10%);
|
||||
$successBackground: $green;
|
||||
$successBorder: transparent;
|
||||
|
||||
$infoText: $blue;
|
||||
$infoBackground: $blue-dark;
|
||||
$infoBorder: transparent;
|
||||
$alert-error-bg: linear-gradient(90deg, #d44939, #e0603d);
|
||||
$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274);
|
||||
$alert-warning-bg: linear-gradient(90deg, #d44939, #e0603d);
|
||||
$alert-info-bg: $blue-dark;
|
||||
|
||||
// popover
|
||||
$popover-bg: $gray-5;
|
||||
$popover-color: $text-color;
|
||||
$popover-border-color: $gray-3;
|
||||
|
||||
$popover-help-bg: $blue-dark;
|
||||
$popover-help-color: $gray-6;
|
||||
$popover-error-bg: $btn-danger-bg;
|
||||
|
||||
// Tooltips and popovers
|
||||
// -------------------------
|
||||
|
@ -31,21 +31,21 @@ cite { font-style: normal; }
|
||||
a.muted:hover,
|
||||
a.muted:focus { color: darken($text-muted, 10%); }
|
||||
|
||||
.text-warning { color: $state-warning-text; }
|
||||
.text-warning { color: $warning-text-color; }
|
||||
a.text-warning:hover,
|
||||
a.text-warning:focus { color: darken($state-warning-text, 10%); }
|
||||
a.text-warning:focus { color: darken($warning-text-color, 10%); }
|
||||
|
||||
.text-error { color: $errorText; }
|
||||
.text-error { color: $error-text-color; }
|
||||
a.text-error:hover,
|
||||
a.text-error:focus { color: darken($errorText, 10%); }
|
||||
a.text-error:focus { color: darken($error-text-color, 10%); }
|
||||
|
||||
.text-info { color: $infoText; }
|
||||
.text-info { color: $info-text-color; }
|
||||
a.text-info:hover,
|
||||
a.text-info:focus { color: darken($infoText, 10%); }
|
||||
a.text-info:focus { color: darken($info-text-color, 10%); }
|
||||
|
||||
.text-success { color: $successText; }
|
||||
.text-success { color: $success-text-color; }
|
||||
a.text-success:hover,
|
||||
a.text-success:focus { color: darken($successText, 10%); }
|
||||
a.text-success:focus { color: darken($success-text-color, 10%); }
|
||||
a { cursor: pointer; }
|
||||
|
||||
a:focus {
|
||||
@ -134,7 +134,7 @@ small,
|
||||
mark,
|
||||
.mark {
|
||||
padding: .2em;
|
||||
background-color: $state-warning-bg;
|
||||
background: $alert-warning-bg;
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,60 +7,57 @@
|
||||
// -------------------------
|
||||
|
||||
.alert {
|
||||
padding: 0.5rem 2rem 0.5rem 1rem;
|
||||
padding: 1.25rem 2rem 1.25rem 1.5rem;
|
||||
margin-bottom: $line-height-base;
|
||||
text-shadow: 0 1px 0 rgba(255,255,255,.5);
|
||||
background-color: $state-warning-bg;
|
||||
text-shadow: 0 2px 0 rgba(255,255,255,.5);
|
||||
background: $alert-error-bg;
|
||||
position: relative;
|
||||
color: $white;
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,.5);
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,.2);
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
// Alternate styles
|
||||
// -------------------------
|
||||
|
||||
.alert-success {
|
||||
background-color: $successBackground;
|
||||
background: $alert-success-bg;
|
||||
}
|
||||
|
||||
.alert-danger,
|
||||
.alert-error {
|
||||
background-color: $errorBackground;
|
||||
background: $alert-error-bg;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: $infoBackground;
|
||||
background: $alert-info-bg;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: $state-warning-bg;
|
||||
background: $alert-warning-bg;
|
||||
}
|
||||
|
||||
.page-alert-list {
|
||||
z-index: 8000;
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 56px;
|
||||
right: 10px;
|
||||
top: 60px;
|
||||
}
|
||||
|
||||
.alert-close {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
background: $white;
|
||||
border-radius: 50%;
|
||||
padding: 0 0 0 1rem;
|
||||
border: none;
|
||||
font-size: 1.1rem;
|
||||
color: $dark-4;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.fa {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
align-self: flex-end;
|
||||
font-size: 1.5rem;
|
||||
color: rgba(255,255,255,.75)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,3 +65,18 @@
|
||||
font-weight: $font-weight-semi-bold;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
padding: 0 1rem 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
.fa {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-body {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
background-color: $dropdownBackground !important;
|
||||
color: $dropdownLinkColor !important;
|
||||
border: 1px solid $dropdownBorder !important;
|
||||
width: 320px !important;
|
||||
width: 550px !important;
|
||||
|
||||
.ace_scroller {
|
||||
.ace_selected, .ace_active-line, .ace_line-hover {
|
||||
@ -77,3 +77,7 @@ $doc-font-size: $font-size-sm;
|
||||
.ace_tooltip {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ace_hidden-cursors .ace_cursor {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ $useDropShadow: false;
|
||||
$attachmentOffset: 0%;
|
||||
$easing: cubic-bezier(0, 0, 0.265, 1.00);
|
||||
|
||||
@include drop-theme("error", $errorBackground, $popover-color);
|
||||
@include drop-theme("error", $popover-error-bg, $popover-color);
|
||||
@include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color);
|
||||
@include drop-theme("help", $popover-help-bg, $popover-help-color);
|
||||
|
||||
|
@ -164,7 +164,7 @@ div.flot-text {
|
||||
&--error {
|
||||
display: block;
|
||||
color: $text-color;
|
||||
@include panel-corner-color($errorBackground);
|
||||
@include panel-corner-color($popover-error-bg);
|
||||
.fa:before {
|
||||
content: "\f12a";
|
||||
}
|
||||
|
@ -167,6 +167,20 @@ define([
|
||||
var res = kbn.calculateInterval(range, 900, '>15ms');
|
||||
expect(res.interval).to.be('15ms');
|
||||
});
|
||||
|
||||
it('1d 1 resolution', function() {
|
||||
var range = { from: dateMath.parse('now-1d'), to: dateMath.parse('now') };
|
||||
var res = kbn.calculateInterval(range, 1, null);
|
||||
expect(res.interval).to.be('1d');
|
||||
expect(res.intervalMs).to.be(86400000);
|
||||
});
|
||||
|
||||
it('86399s 1 resolution', function() {
|
||||
var range = { from: dateMath.parse('now-86390s'), to: dateMath.parse('now') };
|
||||
var res = kbn.calculateInterval(range, 1, null);
|
||||
expect(res.interval).to.be('12h');
|
||||
expect(res.intervalMs).to.be(43200000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hex', function() {
|
||||
|
@ -29,11 +29,14 @@
|
||||
|
||||
<div class="page-alert-list">
|
||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
|
||||
<div class="alert-icon"><i class="{{alert.icon}}"></i></div>
|
||||
<div class="alert-body">
|
||||
<div class="alert-title">{{alert.title}}</div>
|
||||
<div class="alert-text" ng-bind='alert.text'></div>
|
||||
</div>
|
||||
<button type="button" class="alert-close" ng-click="dashAlerts.clear(alert)">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
<i class="fa fa fa-remove"></i>
|
||||
</button>
|
||||
<div class="alert-title">{{alert.title}}</div>
|
||||
<div ng-bind='alert.text'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user