mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into heatmap-refactoring2
This commit is contained in:
@@ -10,7 +10,6 @@
|
|||||||
* **Prometheus**: Support table response formats (column per label) [#6140](https://github.com/grafana/grafana/issues/6140), thx [@mtanda](https://github.com/mtanda)
|
* **Prometheus**: Support table response formats (column per label) [#6140](https://github.com/grafana/grafana/issues/6140), thx [@mtanda](https://github.com/mtanda)
|
||||||
* **Single Stat Panel**: support for non time series data [#6564](https://github.com/grafana/grafana/issues/6564)
|
* **Single Stat Panel**: support for non time series data [#6564](https://github.com/grafana/grafana/issues/6564)
|
||||||
|
|
||||||
|
|
||||||
## Minor Enchancements
|
## Minor Enchancements
|
||||||
|
|
||||||
* **Prometheus**: Make Prometheus query field a textarea [#7663](https://github.com/grafana/grafana/issues/7663), thx [@hagen1778](https://github.com/hagen1778)
|
* **Prometheus**: Make Prometheus query field a textarea [#7663](https://github.com/grafana/grafana/issues/7663), thx [@hagen1778](https://github.com/hagen1778)
|
||||||
@@ -22,12 +21,16 @@
|
|||||||
* **Templating**: Data source variable now supports multi value and panel repeats [#7030](https://github.com/grafana/grafana/issues/7030) thx [@mtanda](https://github.com/mtanda)
|
* **Templating**: Data source variable now supports multi value and panel repeats [#7030](https://github.com/grafana/grafana/issues/7030) thx [@mtanda](https://github.com/mtanda)
|
||||||
* **Telegram**: Telegram alert is not sending metric and legend. [#8110](https://github.com/grafana/grafana/issues/8110), thx [@bashgeek](https://github.com/bashgeek)
|
* **Telegram**: Telegram alert is not sending metric and legend. [#8110](https://github.com/grafana/grafana/issues/8110), thx [@bashgeek](https://github.com/bashgeek)
|
||||||
* **Graph**: Support dashed lines [#514](https://github.com/grafana/grafana/issues/514), thx [@smalik03](https://github.com/smalik03)
|
* **Graph**: Support dashed lines [#514](https://github.com/grafana/grafana/issues/514), thx [@smalik03](https://github.com/smalik03)
|
||||||
|
* **Table**: Support to change column header text [#3551](https://github.com/grafana/grafana/issues/3551)
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **Table Panel**: Fixed annotation display in table panel, [#8023](https://github.com/grafana/grafana/issues/8023)
|
* **Table Panel**: Fixed annotation display in table panel, [#8023](https://github.com/grafana/grafana/issues/8023)
|
||||||
* **Dashboard**: If refresh is blocked due to tab not visible, then refresh when it becomes visible [#8076](https://github.com/grafana/grafana/issues/8076) thanks [@SimenB](https://github.com/SimenB)
|
* **Dashboard**: If refresh is blocked due to tab not visible, then refresh when it becomes visible [#8076](https://github.com/grafana/grafana/issues/8076) thanks [@SimenB](https://github.com/SimenB)
|
||||||
* **Snapshots**: Fixed problem with annotations & snapshots [#7659](https://github.com/grafana/grafana/issues/7659)
|
* **Snapshots**: Fixed problem with annotations & snapshots [#7659](https://github.com/grafana/grafana/issues/7659)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
* **Elasticsearch**: Changed elasticsearch Terms aggregation to default to Min Doc Count to 1, and sort order to Top [#8321](https://github.com/grafana/grafana/issues/8321)
|
||||||
|
|
||||||
# 4.2.0 (2017-03-22)
|
# 4.2.0 (2017-03-22)
|
||||||
## Minor Enhancements
|
## Minor Enhancements
|
||||||
* **Templates**: Prevent use of the prefix `__` for templates in web UI [#7678](https://github.com/grafana/grafana/issues/7678)
|
* **Templates**: Prevent use of the prefix `__` for templates in web UI [#7678](https://github.com/grafana/grafana/issues/7678)
|
||||||
|
|||||||
@@ -13,29 +13,26 @@ weight = 10
|
|||||||
|
|
||||||
# Using AWS CloudWatch in Grafana
|
# Using AWS CloudWatch in Grafana
|
||||||
|
|
||||||
Grafana ships with built in support for CloudWatch. You just have to add it as a data source and you will
|
Grafana ships with built in support for CloudWatch. You just have to add it as a data source and you will be ready to build dashboards for you CloudWatch metrics.
|
||||||
be ready to build dashboards for you CloudWatch metrics.
|
|
||||||
|
|
||||||
## Adding the data source
|
## Adding the data source to Grafana
|
||||||

|
|
||||||
|
|
||||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
1. Open the side menu by clicking the Grafana icon in the top header.
|
||||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||||
|
3. Click the `+ Add data source` button in the top header.
|
||||||
|
4. Select `Cloudwatch` from the *Type* dropdown.
|
||||||
|
|
||||||
> NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization.
|
> NOTE: If at any moment you have issues with getting this datasource to work and Grafana is giving you undescriptive errors then don't
|
||||||
|
forget to check your log file (try looking in /var/log/grafana/grafana.log).
|
||||||
3. Click the `Add new` link in the top header.
|
|
||||||
4. Select `CloudWatch` from the dropdown.
|
|
||||||
> NOTE: If at any moment you have issues with getting this datasource to work and grafana is giving you undescriptive errors then dont forget to check your log file (try looking in /var/log/grafana/).
|
|
||||||
|
|
||||||
Name | Description
|
Name | Description
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
|
*Name* | The data source name. This is how you refer to the data source in panels & queries.
|
||||||
Default | Default data source means that it will be pre-selected for new panels.
|
*Default* | Default data source means that it will be pre-selected for new panels.
|
||||||
Credentials profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default. This option was introduced in Grafana 2.5.1
|
*Credentials* profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default.
|
||||||
Default Region | Used in query editor to set region (can be changed on per query basis)
|
*Default Region* | Used in query editor to set region (can be changed on per query basis)
|
||||||
Custom Metrics namespace | Specify the CloudWatch namespace of Custom metrics
|
*Custom Metrics namespace* | Specify the CloudWatch namespace of Custom metrics
|
||||||
Assume Role Arn | Specify the ARN of the role to assume
|
*Assume Role Arn* | Specify the ARN of the role to assume
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
@@ -61,49 +58,64 @@ Example content:
|
|||||||
|
|
||||||
## Metric Query Editor
|
## Metric Query Editor
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
You need to specify a namespace, metric, at least one stat, and at least one dimension.
|
You need to specify a namespace, metric, at least one stat, and at least one dimension.
|
||||||
|
|
||||||
## Templated queries
|
## Templated queries
|
||||||
CloudWatch Datasource Plugin provides the following functions in `Variables values query` field in Templating Editor to query `region`, `namespaces`, `metric names` and `dimension keys/values` on the CloudWatch.
|
|
||||||
|
Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place.
|
||||||
|
Variables are shown as dropdown select boxes at the top of the dashboard. These dropdowns makes it easy to change the data
|
||||||
|
being displayed in your dashboard.
|
||||||
|
|
||||||
|
Checkout the [Templating]({{< relref "reference/templating.md" >}}) documentation for an introduction to the templating feature and the different
|
||||||
|
types of template variables.
|
||||||
|
|
||||||
|
### Query variable
|
||||||
|
|
||||||
|
CloudWatch Datasource Plugin provides the following queries you can specify in the `Query` field in the Variable
|
||||||
|
edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
|
||||||
|
and `dimension keys/values`.
|
||||||
|
|
||||||
Name | Description
|
Name | Description
|
||||||
------- | --------
|
------- | --------
|
||||||
`regions()` | Returns a list of regions AWS provides their service.
|
*regions()* | Returns a list of regions AWS provides their service.
|
||||||
`namespaces()` | Returns a list of namespaces CloudWatch support.
|
*namespaces()* | Returns a list of namespaces CloudWatch support.
|
||||||
`metrics(namespace, [region])` | Returns a list of metrics in the namespace. (specify region for custom metrics)
|
*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region for custom metrics)
|
||||||
`dimension_keys(namespace)` | Returns a list of dimension keys in the namespace.
|
*dimension_keys(namespace)* | Returns a list of dimension keys in the namespace.
|
||||||
`dimension_values(region, namespace, metric, dimension_key)` | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
|
*dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
|
||||||
`ebs_volume_ids(region, instance_id)` | Returns a list of volume id matching the specified `region`, `instance_id`.
|
*ebs_volume_ids(region, instance_id)* | Returns a list of volume id matching the specified `region`, `instance_id`.
|
||||||
`ec2_instance_attribute(region, attribute_name, filters)` | Returns a list of attribute matching the specified `region`, `attribute_name`, `filters`.
|
*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attribute matching the specified `region`, `attribute_name`, `filters`.
|
||||||
|
|
||||||
For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
|
For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
|
||||||
|
|
||||||
## Example templated Queries
|
#### Examples templated Queries
|
||||||
|
|
||||||
Example dimension queries which will return list of resources for individual AWS Services:
|
Example dimension queries which will return list of resources for individual AWS Services:
|
||||||
|
|
||||||
Service | Query
|
Query | Service
|
||||||
------- | -----
|
------- | -----
|
||||||
ELB | `dimension_values(us-east-1,AWS/ELB,RequestCount,LoadBalancerName)`
|
*dimension_values(us-east-1,AWS/ELB,RequestCount,LoadBalancerName)* | ELB
|
||||||
ElastiCache | `dimension_values(us-east-1,AWS/ElastiCache,CPUUtilization,CacheClusterId)`
|
*dimension_values(us-east-1,AWS/ElastiCache,CPUUtilization,CacheClusterId)* | ElastiCache
|
||||||
RedShift | `dimension_values(us-east-1,AWS/Redshift,CPUUtilization,ClusterIdentifier)`
|
*dimension_values(us-east-1,AWS/Redshift,CPUUtilization,ClusterIdentifier)* | RedShift
|
||||||
RDS | `dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)`
|
*dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)* | RDS
|
||||||
S3 | `dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)`
|
*dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)* | S3
|
||||||
|
|
||||||
## ec2_instance_attribute JSON filters
|
#### ec2_instance_attribute JSON filters
|
||||||
|
|
||||||
The `ec2_instance_attribute` query take `filters` in JSON format.
|
The `ec2_instance_attribute` query take `filters` in JSON format.
|
||||||
You can specify [pre-defined filters of ec2:DescribeInstances](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html).
|
You can specify [pre-defined filters of ec2:DescribeInstances](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html).
|
||||||
Specify like `{ filter_name1: [ filter_value1 ], filter_name2: [ filter_value2 ] }`
|
|
||||||
|
Filters syntax:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{ filter_name1: [ filter_value1 ], filter_name2: [ filter_value2 ] }
|
||||||
|
```
|
||||||
|
|
||||||
Example `ec2_instance_attribute()` query
|
Example `ec2_instance_attribute()` query
|
||||||
|
|
||||||
ec2_instance_attribute(us-east-1, InstanceId, { "tag:Environment": [ "production" ] })
|
ec2_instance_attribute(us-east-1, InstanceId, { "tag:Environment": [ "production" ] })
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Cost
|
## Cost
|
||||||
|
|
||||||
Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this,
|
Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this,
|
||||||
|
|||||||
@@ -85,8 +85,9 @@ The column styles allow you control how dates and numbers are formatted.
|
|||||||
|
|
||||||
1. `Name or regex`: The Name or Regex field controls what columns the rule should be applied to. The regex or name filter will be matched against the column name not against column values.
|
1. `Name or regex`: The Name or Regex field controls what columns the rule should be applied to. The regex or name filter will be matched against the column name not against column values.
|
||||||
2. `Type`: The three supported types of types are `Number`, `String` and `Date`.
|
2. `Type`: The three supported types of types are `Number`, `String` and `Date`.
|
||||||
3. `Format`: Specify date format. Only available when `Type` is set to `Date`.
|
3. `Title`: Title for the column, when using a Regex the title can include replacement strings like `$1`.
|
||||||
4. `Coloring` and `Thresholds`: Specify color mode and thresholds limits.
|
4. `Format`: Specify date format. Only available when `Type` is set to `Date`.
|
||||||
5. `Unit` and `Decimals`: Specify unit and decimal precision for numbers.
|
5. `Coloring` and `Thresholds`: Specify color mode and thresholds limits.
|
||||||
6. `Add column style rule`: Add new column rule.
|
6. `Unit` and `Decimals`: Specify unit and decimal precision for numbers.
|
||||||
|
7. `Add column style rule`: Add new column rule.
|
||||||
|
|
||||||
|
|||||||
@@ -19,26 +19,28 @@ Creates a new dashboard or updates an existing dashboard.
|
|||||||
|
|
||||||
**Example Request for new dashboard**:
|
**Example Request for new dashboard**:
|
||||||
|
|
||||||
POST /api/dashboards/db HTTP/1.1
|
```http
|
||||||
Accept: application/json
|
POST /api/dashboards/db HTTP/1.1
|
||||||
Content-Type: application/json
|
Accept: application/json
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
|
||||||
{
|
```
|
||||||
"dashboard": {
|
|
||||||
"id": null,
|
JSON Body schema:
|
||||||
"title": "Production Overview",
|
|
||||||
"tags": [ "templated" ],
|
- **dashboard** – The complete dashboard model, id = null to create a new dashboard
|
||||||
"timezone": "browser",
|
- **overwrite** – Set to true if you want to overwrite existing dashboard with newer version or with same dashboard title.
|
||||||
"rows": [
|
|
||||||
{
|
**Example Response**:
|
||||||
}
|
|
||||||
],
|
```http
|
||||||
"schemaVersion": 6,
|
HTTP/1.1 200 OK
|
||||||
"version": 0
|
Content-Type: application/json; charset=UTF-8
|
||||||
},
|
Content-Length: 78
|
||||||
"overwrite": false
|
|
||||||
}
|
```
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
- **200** – Created
|
- **200** – Created
|
||||||
@@ -47,15 +49,17 @@ JSON Body schema:
|
|||||||
- **412** – Precondition failed
|
- **412** – Precondition failed
|
||||||
|
|
||||||
The **412** status code is used when a newer dashboard already exists (newer, its version is greater than the version that was sent). The
|
The **412** status code is used when a newer dashboard already exists (newer, its version is greater than the version that was sent). The
|
||||||
HTTP/1.1 200 OK
|
same status code is also used if another dashboard exists with the same title. The response body will look like this:
|
||||||
Content-Type: application/json; charset=UTF-8
|
|
||||||
Content-Length: 78
|
```http
|
||||||
|
HTTP/1.1 412 Precondition Failed
|
||||||
Content-Type: application/json; charset=UTF-8
|
Content-Type: application/json; charset=UTF-8
|
||||||
{
|
Content-Length: 97
|
||||||
"slug": "production-overview",
|
|
||||||
"status": "success",
|
```
|
||||||
"version": 1
|
|
||||||
}
|
In in case of title already exists the `status` property will be `name-exists`.
|
||||||
|
|
||||||
## Get dashboard
|
## Get dashboard
|
||||||
|
|
||||||
`GET /api/dashboards/db/:slug`
|
`GET /api/dashboards/db/:slug`
|
||||||
@@ -67,14 +71,16 @@ Status Codes:
|
|||||||
```http
|
```http
|
||||||
GET /api/dashboards/db/production-overview HTTP/1.1
|
GET /api/dashboards/db/production-overview HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
HTTP/1.1 412 Precondition Failed
|
Content-Type: application/json
|
||||||
Content-Type: application/json; charset=UTF-8
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
Content-Length: 97
|
```
|
||||||
|
|
||||||
**Example Response**:
|
**Example Response**:
|
||||||
{
|
|
||||||
"message": "The dashboard has been changed by someone else",
|
```http
|
||||||
"status": "version-mismatch"
|
HTTP/1.1 200
|
||||||
}
|
Content-Type: application/json
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delete dashboard
|
## Delete dashboard
|
||||||
@@ -86,34 +92,38 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver
|
|||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/dashboards/db/production-overview HTTP/1.1
|
DELETE /api/dashboards/db/test HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
**Example Response**:
|
**Example Response**:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
|
||||||
"meta": {
|
## Gets the home dashboard
|
||||||
"isStarred": false,
|
|
||||||
"slug": "production-overview"
|
`GET /api/dashboards/home`
|
||||||
},
|
|
||||||
"dashboard": {
|
Will return the home dashboard.
|
||||||
"id": null,
|
|
||||||
"title": "Production Overview",
|
**Example Request**:
|
||||||
"tags": [ "templated" ],
|
|
||||||
"timezone": "browser",
|
GET /api/dashboards/home HTTP/1.1
|
||||||
"rows": [
|
Accept: application/json
|
||||||
{
|
Content-Type: application/json
|
||||||
}
|
|
||||||
],
|
|
||||||
"schemaVersion": 6,
|
|
||||||
"version": 0
|
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
}
|
|
||||||
|
**Example Response**:
|
||||||
|
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"isHome":true,
|
"isHome":true,
|
||||||
@@ -123,17 +133,21 @@ The above will delete the dashboard with the specified slug. The slug is the url
|
|||||||
"slug":"",
|
"slug":"",
|
||||||
"expires":"0001-01-01T00:00:00Z",
|
"expires":"0001-01-01T00:00:00Z",
|
||||||
"created":"0001-01-01T00:00:00Z"
|
"created":"0001-01-01T00:00:00Z"
|
||||||
DELETE /api/dashboards/db/test HTTP/1.1
|
},
|
||||||
Accept: application/json
|
"dashboard": {
|
||||||
Content-Type: application/json
|
"editable":false,
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
"hideControls":true,
|
||||||
|
"nav":[
|
||||||
|
{
|
||||||
"enable":false,
|
"enable":false,
|
||||||
"type":"timepicker"
|
"type":"timepicker"
|
||||||
}
|
}
|
||||||
HTTP/1.1 200
|
],
|
||||||
Content-Type: application/json
|
"rows": [
|
||||||
|
{
|
||||||
|
|
||||||
{"title": "Test"}
|
}
|
||||||
|
],
|
||||||
"style":"dark",
|
"style":"dark",
|
||||||
"tags":[],
|
"tags":[],
|
||||||
"templating":{
|
"templating":{
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ installation.
|
|||||||
|
|
||||||
## Install Stable
|
## Install Stable
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.2.0_amd64.deb
|
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.2.0_amd64.deb
|
||||||
$ sudo apt-get install -y adduser libfontconfig
|
sudo apt-get install -y adduser libfontconfig
|
||||||
$ sudo dpkg -i grafana_4.2.0_amd64.deb
|
sudo dpkg -i grafana_4.2.0_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
## APT Repository
|
## APT Repository
|
||||||
@@ -43,18 +43,24 @@ candidates.
|
|||||||
Then add the [Package Cloud](https://packagecloud.io/grafana) key. This
|
Then add the [Package Cloud](https://packagecloud.io/grafana) key. This
|
||||||
allows you to install signed packages.
|
allows you to install signed packages.
|
||||||
|
|
||||||
$ curl https://packagecloud.io/gpg.key | sudo apt-key add -
|
```bash
|
||||||
|
curl https://packagecloud.io/gpg.key | sudo apt-key add -
|
||||||
|
```
|
||||||
|
|
||||||
Update your Apt repositories and install Grafana
|
Update your Apt repositories and install Grafana
|
||||||
|
|
||||||
$ sudo apt-get update
|
```bash
|
||||||
$ sudo apt-get install grafana
|
sudo apt-get update
|
||||||
|
sudo apt-get install grafana
|
||||||
|
```
|
||||||
|
|
||||||
On some older versions of Ubuntu and Debian you may need to install the
|
On some older versions of Ubuntu and Debian you may need to install the
|
||||||
`apt-transport-https` package which is needed to fetch packages over
|
`apt-transport-https` package which is needed to fetch packages over
|
||||||
HTTPS.
|
HTTPS.
|
||||||
|
|
||||||
$ sudo apt-get install -y apt-transport-https
|
```bash
|
||||||
|
sudo apt-get install -y apt-transport-https
|
||||||
|
```
|
||||||
|
|
||||||
## Package details
|
## Package details
|
||||||
|
|
||||||
@@ -70,7 +76,9 @@ HTTPS.
|
|||||||
|
|
||||||
Start Grafana by running:
|
Start Grafana by running:
|
||||||
|
|
||||||
$ sudo service grafana-server start
|
```bash
|
||||||
|
sudo service grafana-server start
|
||||||
|
```
|
||||||
|
|
||||||
This will start the `grafana-server` process as the `grafana` user,
|
This will start the `grafana-server` process as the `grafana` user,
|
||||||
which was created during the package installation. The default HTTP port
|
which was created during the package installation. The default HTTP port
|
||||||
@@ -78,19 +86,25 @@ is `3000` and default user and group is `admin`.
|
|||||||
|
|
||||||
To configure the Grafana server to start at boot time:
|
To configure the Grafana server to start at boot time:
|
||||||
|
|
||||||
$ sudo update-rc.d grafana-server defaults
|
```bash
|
||||||
|
sudo update-rc.d grafana-server defaults
|
||||||
|
```
|
||||||
|
|
||||||
## Start the server (via systemd)
|
## Start the server (via systemd)
|
||||||
|
|
||||||
To start the service using systemd:
|
To start the service using systemd:
|
||||||
|
|
||||||
$ systemctl daemon-reload
|
```bash
|
||||||
$ systemctl start grafana-server
|
systemctl daemon-reload
|
||||||
$ systemctl status grafana-server
|
systemctl start grafana-server
|
||||||
|
systemctl status grafana-server
|
||||||
|
```
|
||||||
|
|
||||||
Enable the systemd service so that Grafana starts at boot.
|
Enable the systemd service so that Grafana starts at boot.
|
||||||
|
|
||||||
sudo systemctl enable grafana-server.service
|
```bash
|
||||||
|
sudo systemctl enable grafana-server.service
|
||||||
|
```
|
||||||
|
|
||||||
## Environment file
|
## Environment file
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function (_, $, coreModule) {
|
|||||||
|
|
||||||
$scope.source = function(query, callback) {
|
$scope.source = function(query, callback) {
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
$scope.getOptions({ measurementFilter: query }).then(function(altSegments) {
|
$scope.getOptions({ $query: query }).then(function(altSegments) {
|
||||||
$scope.altSegments = altSegments;
|
$scope.altSegments = altSegments;
|
||||||
options = _.map($scope.altSegments, function(alt) { return alt.value; });
|
options = _.map($scope.altSegments, function(alt) { return alt.value; });
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
attrs: {dashboard: "ctrl.dashboard", panel: "panel", row: "ctrl.row"},
|
attrs: {dashboard: "ctrl.dashboard", panel: "panel", row: "ctrl.row"},
|
||||||
};
|
};
|
||||||
|
|
||||||
var panelElemName = 'panel-' + scope.panel.type;
|
|
||||||
let panelInfo = config.panels[scope.panel.type];
|
let panelInfo = config.panels[scope.panel.type];
|
||||||
var panelCtrlPromise = Promise.resolve(UnknownPanelCtrl);
|
var panelCtrlPromise = Promise.resolve(UnknownPanelCtrl);
|
||||||
if (panelInfo) {
|
if (panelInfo) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function exportTableDataToCsv(table) {
|
|||||||
var text = 'sep=;\n';
|
var text = 'sep=;\n';
|
||||||
// add header
|
// add header
|
||||||
_.each(table.columns, function(column) {
|
_.each(table.columns, function(column) {
|
||||||
text += column.text + ';';
|
text += (column.title || column.text) + ';';
|
||||||
});
|
});
|
||||||
text += '\n';
|
text += '\n';
|
||||||
// process data
|
// process data
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ function (angular, _, queryDef) {
|
|||||||
|
|
||||||
switch($scope.agg.type) {
|
switch($scope.agg.type) {
|
||||||
case 'terms': {
|
case 'terms': {
|
||||||
settings.order = settings.order || "asc";
|
settings.order = settings.order || "desc";
|
||||||
settings.size = settings.size || "10";
|
settings.size = settings.size || "10";
|
||||||
settings.min_doc_count = settings.min_doc_count || 0;
|
settings.min_doc_count = settings.min_doc_count || 1;
|
||||||
settings.orderBy = settings.orderBy || "_term";
|
settings.orderBy = settings.orderBy || "_term";
|
||||||
|
|
||||||
if (settings.size !== '0') {
|
if (settings.size !== '0') {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function (queryDef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query.script_fields = {},
|
query.script_fields = {},
|
||||||
query.fielddata_fields = [this.timeField];
|
query.docvalue_fields = [this.timeField];
|
||||||
return query;
|
return query;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<label class="gf-form-label query-keyword width-7">FROM</label>
|
<label class="gf-form-label query-keyword width-7">FROM</label>
|
||||||
|
|
||||||
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
|
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
|
||||||
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements(measurementFilter)" on-change="ctrl.measurementChanged()"></metric-segment>
|
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements($query)" on-change="ctrl.measurementChanged()"></metric-segment>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ describe('grafanaHeatmap', function () {
|
|||||||
getTimezone: sinon.stub().returns('utc')
|
getTimezone: sinon.stub().returns('utc')
|
||||||
},
|
},
|
||||||
range: {
|
range: {
|
||||||
from: moment.utc("01 Mar 2017 10:00:00"),
|
from: moment.utc("01 Mar 2017 10:00:00", 'DD MMM YYYY HH:mm:ss'),
|
||||||
to: moment.utc("01 Mar 2017 11:00:00"),
|
to: moment.utc("01 Mar 2017 11:00:00", 'DD MMM YYYY HH:mm:ss'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,5 +263,5 @@ function getTicks(element, axisSelector) {
|
|||||||
|
|
||||||
function formatLocalTime(timeStr) {
|
function formatLocalTime(timeStr) {
|
||||||
let format = "HH:mm";
|
let format = "HH:mm";
|
||||||
return moment.utc(timeStr).local().format(format);
|
return moment.utc(timeStr, 'DD MMM YYYY HH:mm:ss').local().format(format);
|
||||||
}
|
}
|
||||||
|
|||||||
99
public/app/plugins/panel/table/column_options.html
Normal file
99
public/app/plugins/panel/table/column_options.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
<div class="edit-tab-with-sidemenu">
|
||||||
|
<aside class="edit-sidemenu-aside">
|
||||||
|
<ul class="edit-sidemenu">
|
||||||
|
<li ng-repeat="style in editor.panel.styles" ng-class="{active: editor.activeStyleIndex === $index}">
|
||||||
|
<a ng-click="editor.activeStyleIndex = $index" >{{style.pattern || 'New rule'}}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="pointer" ng-click="editor.addColumnStyle()">
|
||||||
|
<i class="fa fa-plus"></i> Add
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="edit-tab-content" ng-repeat="style in editor.panel.styles" ng-if="editor.activeStyleIndex === $index">
|
||||||
|
|
||||||
|
<div class="section gf-form-group">
|
||||||
|
<h5 class="section-heading">Options</h5>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-13">Apply to columns named</label>
|
||||||
|
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur data-placement="right">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-if="style.type !== 'hidden'">
|
||||||
|
<label class="gf-form-label width-13">Column Header</label>
|
||||||
|
<input type="text" class="gf-form-input width-13" ng-model="style.alias" ng-change="editor.render()" ng-model-onblur placeholder="Override header label">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section gf-form-group">
|
||||||
|
<h5 class="section-heading">Type</h5>
|
||||||
|
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Type</label>
|
||||||
|
<div class="gf-form-select-wrapper width-10">
|
||||||
|
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-if="style.type === 'date'">
|
||||||
|
<label class="gf-form-label width-8">Date Format</label>
|
||||||
|
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="style.type === 'string'">
|
||||||
|
<gf-form-switch class="gf-form" label-class="width-8" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize" change="editor.render()"></gf-form-switch>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="style.type === 'number'">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Unit</label>
|
||||||
|
<div class="gf-form-dropdown-typeahead width-10" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Decimals</label>
|
||||||
|
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()" ng-model-onblur>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section gf-form-group" ng-if="style.type === 'number'">
|
||||||
|
<h5 class="section-heading">Thresholds</h5>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Thresholds<tip>Comma separated values</tip></label>
|
||||||
|
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()" array-join ng-model-onblur>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Color Mode</label>
|
||||||
|
<div class="gf-form-select-wrapper width-10">
|
||||||
|
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-8">Colors</label>
|
||||||
|
<span class="gf-form-label">
|
||||||
|
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()"></spectrum-picker>
|
||||||
|
</span>
|
||||||
|
<span class="gf-form-label">
|
||||||
|
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()"></spectrum-picker>
|
||||||
|
</span>
|
||||||
|
<span class="gf-form-label">
|
||||||
|
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()"></spectrum-picker>
|
||||||
|
</span>
|
||||||
|
<div class="gf-form-label">
|
||||||
|
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<button class="btn btn-danger btn-small" ng-click="editor.removeColumnStyle(style)">
|
||||||
|
<i class="fa fa-trash"></i> Remove Rule
|
||||||
|
</button>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
</div>
|
||||||
120
public/app/plugins/panel/table/column_options.ts
Normal file
120
public/app/plugins/panel/table/column_options.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import $ from 'jquery';
|
||||||
|
import moment from 'moment';
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
|
||||||
|
export class ColumnOptionsCtrl {
|
||||||
|
panel: any;
|
||||||
|
panelCtrl: any;
|
||||||
|
colorModes: any;
|
||||||
|
columnStyles: any;
|
||||||
|
columnTypes: any;
|
||||||
|
fontSizes: any;
|
||||||
|
dateFormats: any;
|
||||||
|
addColumnSegment: any;
|
||||||
|
unitFormats: any;
|
||||||
|
getColumnNames: any;
|
||||||
|
activeStyleIndex: number;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
constructor($scope, private $q, private uiSegmentSrv) {
|
||||||
|
$scope.editor = this;
|
||||||
|
this.activeStyleIndex = 0;
|
||||||
|
this.panelCtrl = $scope.ctrl;
|
||||||
|
this.panel = this.panelCtrl.panel;
|
||||||
|
this.unitFormats = kbn.getUnitFormats();
|
||||||
|
this.colorModes = [
|
||||||
|
{text: 'Disabled', value: null},
|
||||||
|
{text: 'Cell', value: 'cell'},
|
||||||
|
{text: 'Value', value: 'value'},
|
||||||
|
{text: 'Row', value: 'row'},
|
||||||
|
];
|
||||||
|
this.columnTypes = [
|
||||||
|
{text: 'Number', value: 'number'},
|
||||||
|
{text: 'String', value: 'string'},
|
||||||
|
{text: 'Date', value: 'date'},
|
||||||
|
{text: 'Hidden', value: 'hidden'}
|
||||||
|
];
|
||||||
|
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
|
||||||
|
this.dateFormats = [
|
||||||
|
{text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'},
|
||||||
|
{text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'},
|
||||||
|
{text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.getColumnNames = () => {
|
||||||
|
if (!this.panelCtrl.table) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return _.map(this.panelCtrl.table.columns, function(col: any) {
|
||||||
|
return col.text;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.panelCtrl.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
setUnitFormat(column, subItem) {
|
||||||
|
column.unit = subItem.value;
|
||||||
|
this.panelCtrl.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
addColumnStyle() {
|
||||||
|
var newStyleRule = {
|
||||||
|
unit: 'short',
|
||||||
|
type: 'number',
|
||||||
|
alias: '',
|
||||||
|
decimals: 2,
|
||||||
|
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||||
|
colorMode: null,
|
||||||
|
pattern: '',
|
||||||
|
dateFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
thresholds: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
var styles = this.panel.styles;
|
||||||
|
var stylesCount = styles.length;
|
||||||
|
var indexToInsert = stylesCount;
|
||||||
|
|
||||||
|
// check if last is a catch all rule, then add it before that one
|
||||||
|
if (stylesCount > 0) {
|
||||||
|
var last = styles[stylesCount-1];
|
||||||
|
if (last.pattern === '/.*/') {
|
||||||
|
indexToInsert = stylesCount-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.splice(indexToInsert, 0, newStyleRule);
|
||||||
|
this.activeStyleIndex = indexToInsert;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeColumnStyle(style) {
|
||||||
|
this.panel.styles = _.without(this.panel.styles, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
invertColorOrder(index) {
|
||||||
|
var ref = this.panel.styles[index].colors;
|
||||||
|
var copy = ref[0];
|
||||||
|
ref[0] = ref[2];
|
||||||
|
ref[2] = copy;
|
||||||
|
this.panelCtrl.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
export function columnOptionsTab($q, uiSegmentSrv) {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
scope: true,
|
||||||
|
templateUrl: 'public/app/plugins/panel/table/column_options.html',
|
||||||
|
controller: ColumnOptionsCtrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,120 +21,27 @@
|
|||||||
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
|
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<gf-form-switch class="gf-form" label-class="width-10"
|
|
||||||
label="Filter null values"
|
|
||||||
checked="editor.panel.filterNull"
|
|
||||||
on-change="editor.render()"></gf-form-switch>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section gf-form-group">
|
<div class="section gf-form-group">
|
||||||
<h5 class="section-heading">Table Display</h5>
|
<h5 class="section-heading">Paging</h5>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form">
|
||||||
<div class="gf-form">
|
<label class="gf-form-label width-8">Rows per page</label>
|
||||||
<label class="gf-form-label width-8">Rows per page</label>
|
<input type="number" class="gf-form-input width-6"
|
||||||
<input type="number" class="gf-form-input width-6"
|
placeholder="100" data-placement="right"
|
||||||
placeholder="100" data-placement="right"
|
ng-model="editor.panel.pageSize"
|
||||||
ng-model="editor.panel.pageSize"
|
ng-change="editor.render()"
|
||||||
ng-change="editor.render()"
|
ng-model-onblur>
|
||||||
ng-model-onblur>
|
</div>
|
||||||
</div>
|
<gf-form-switch class="gf-form" label-class="width-8" switch-class="max-width-6" label="Scroll" checked="editor.panel.scroll" on-change="editor.render()"></gf-form-switch>
|
||||||
<gf-form-switch class="gf-form" label-class="width-4" label="Scroll" checked="editor.panel.scroll" on-change="editor.render()"></gf-form-switch>
|
<div class="gf-form max-width-17">
|
||||||
<div class="gf-form max-width-17">
|
<label class="gf-form-label width-8">Font size</label>
|
||||||
<label class="gf-form-label width-6">Font size</label>
|
<div class="gf-form-select-wrapper width-6">
|
||||||
<div class="gf-form-select-wrapper max-width-15">
|
<select class="gf-form-input"
|
||||||
<select class="gf-form-input"
|
ng-model="editor.panel.fontSize"
|
||||||
ng-model="editor.panel.fontSize"
|
ng-options="f for f in editor.fontSizes"
|
||||||
ng-options="f for f in editor.fontSizes"
|
ng-change="editor.render()"></select>
|
||||||
ng-change="editor.render()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="editor-row">
|
|
||||||
<div class="section gf-form-group">
|
|
||||||
<h5 class="section-heading">Column Styles</h5>
|
|
||||||
<div ng-repeat="style in editor.panel.styles">
|
|
||||||
<div class="gf-form-inline">
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label">Name or regex</label>
|
|
||||||
<input type="text" class="gf-form-input" ng-model="style.pattern" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label">Type</label>
|
|
||||||
<div class="gf-form-select-wrapper">
|
|
||||||
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form" ng-if="style.type === 'date'">
|
|
||||||
<label class="gf-form-label">Format</label>
|
|
||||||
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
|
|
||||||
</div>
|
|
||||||
<gf-form-switch class="gf-form" label-class="width-8" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize" change="editor.render()"></gf-form-switch>
|
|
||||||
<div class="gf-form gf-form--grow">
|
|
||||||
<div class="gf-form-label gf-form-label--grow"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label">
|
|
||||||
<a class="pointer" ng-click="editor.removeColumnStyle(style)">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-if="style.type === 'number'">
|
|
||||||
<div class="gf-form offset-width-8">
|
|
||||||
<label class="gf-form-label width-8">Unit</label>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<div class="gf-form-dropdown-typeahead" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label">Decimals</label>
|
|
||||||
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()" ng-model-onblur>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label">Coloring</label>
|
|
||||||
<div class="gf-form-select-wrapper">
|
|
||||||
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form gf-form--grow">
|
|
||||||
<div class="gf-form-label gf-form-label--grow"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-if="style.type === 'number'">
|
|
||||||
<div class="gf-form offset-width-8">
|
|
||||||
<label class="gf-form-label width-8">Thresholds<tip>Comma separated values</tip></label>
|
|
||||||
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()" array-join ng-model-onblur>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form">
|
|
||||||
<label class="gf-form-label width-5">Colors</label>
|
|
||||||
<span class="gf-form-label">
|
|
||||||
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()"></spectrum-picker>
|
|
||||||
</span>
|
|
||||||
<span class="gf-form-label">
|
|
||||||
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()"></spectrum-picker>
|
|
||||||
</span>
|
|
||||||
<span class="gf-form-label">
|
|
||||||
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()"></spectrum-picker>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="gf-form gf-form--grow">
|
|
||||||
<div class="gf-form-label gf-form-label--grow">
|
|
||||||
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form-button">
|
|
||||||
<button class="btn btn-inverse" ng-click="editor.addColumnStyle()">
|
|
||||||
<i class="fa fa-plus"></i> Add column style rule
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,13 +13,8 @@ export class TablePanelEditorCtrl {
|
|||||||
panel: any;
|
panel: any;
|
||||||
panelCtrl: any;
|
panelCtrl: any;
|
||||||
transformers: any;
|
transformers: any;
|
||||||
colorModes: any;
|
|
||||||
columnStyles: any;
|
|
||||||
columnTypes: any;
|
|
||||||
fontSizes: any;
|
fontSizes: any;
|
||||||
dateFormats: any;
|
|
||||||
addColumnSegment: any;
|
addColumnSegment: any;
|
||||||
unitFormats: any;
|
|
||||||
getColumnNames: any;
|
getColumnNames: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
@@ -28,37 +23,9 @@ export class TablePanelEditorCtrl {
|
|||||||
this.panelCtrl = $scope.ctrl;
|
this.panelCtrl = $scope.ctrl;
|
||||||
this.panel = this.panelCtrl.panel;
|
this.panel = this.panelCtrl.panel;
|
||||||
this.transformers = transformers;
|
this.transformers = transformers;
|
||||||
this.unitFormats = kbn.getUnitFormats();
|
|
||||||
this.colorModes = [
|
|
||||||
{text: 'Disabled', value: null},
|
|
||||||
{text: 'Cell', value: 'cell'},
|
|
||||||
{text: 'Value', value: 'value'},
|
|
||||||
{text: 'Row', value: 'row'},
|
|
||||||
];
|
|
||||||
this.columnTypes = [
|
|
||||||
{text: 'Number', value: 'number'},
|
|
||||||
{text: 'String', value: 'string'},
|
|
||||||
{text: 'Date', value: 'date'},
|
|
||||||
{text: 'Hidden', value: 'hidden'}
|
|
||||||
];
|
|
||||||
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
|
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
|
||||||
this.dateFormats = [
|
|
||||||
{text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'},
|
|
||||||
{text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'},
|
|
||||||
{text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'},
|
|
||||||
];
|
|
||||||
|
|
||||||
this.addColumnSegment = uiSegmentSrv.newPlusButton();
|
this.addColumnSegment = uiSegmentSrv.newPlusButton();
|
||||||
|
|
||||||
// this is used from bs-typeahead and needs to be instance bound
|
|
||||||
this.getColumnNames = () => {
|
|
||||||
if (!this.panelCtrl.table) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return _.map(this.panelCtrl.table.columns, function(col: any) {
|
|
||||||
return col.text;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnOptions() {
|
getColumnOptions() {
|
||||||
@@ -97,38 +64,6 @@ export class TablePanelEditorCtrl {
|
|||||||
this.panel.columns = _.without(this.panel.columns, column);
|
this.panel.columns = _.without(this.panel.columns, column);
|
||||||
this.panelCtrl.render();
|
this.panelCtrl.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
setUnitFormat(column, subItem) {
|
|
||||||
column.unit = subItem.value;
|
|
||||||
this.panelCtrl.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
addColumnStyle() {
|
|
||||||
var columnStyleDefaults = {
|
|
||||||
unit: 'short',
|
|
||||||
type: 'number',
|
|
||||||
decimals: 2,
|
|
||||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
|
||||||
colorMode: null,
|
|
||||||
pattern: '/.*/',
|
|
||||||
dateFormat: 'YYYY-MM-DD HH:mm:ss',
|
|
||||||
thresholds: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.panel.styles.push(angular.copy(columnStyleDefaults));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeColumnStyle(style) {
|
|
||||||
this.panel.styles = _.without(this.panel.styles, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
invertColorOrder(index) {
|
|
||||||
var ref = this.panel.styles[index].colors;
|
|
||||||
var copy = ref[0];
|
|
||||||
ref[0] = ref[2];
|
|
||||||
ref[2] = copy;
|
|
||||||
this.panelCtrl.render();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th ng-repeat="col in ctrl.table.columns" ng-hide="col.hidden">
|
<th ng-repeat="col in ctrl.table.columns" ng-hide="col.hidden">
|
||||||
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
|
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
|
||||||
{{col.text}}
|
{{col.title}}
|
||||||
<span class="table-panel-table-header-controls" ng-if="col.sort">
|
<span class="table-panel-table-header-controls" ng-if="col.sort">
|
||||||
<i class="fa fa-caret-down" ng-show="col.desc"></i>
|
<i class="fa fa-caret-down" ng-show="col.desc"></i>
|
||||||
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
|
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as FileExport from 'app/core/utils/file_export';
|
|||||||
import {MetricsPanelCtrl} from 'app/plugins/sdk';
|
import {MetricsPanelCtrl} from 'app/plugins/sdk';
|
||||||
import {transformDataToTable} from './transformers';
|
import {transformDataToTable} from './transformers';
|
||||||
import {tablePanelEditor} from './editor';
|
import {tablePanelEditor} from './editor';
|
||||||
|
import {columnOptionsTab} from './column_options';
|
||||||
import {TableRenderer} from './renderer';
|
import {TableRenderer} from './renderer';
|
||||||
|
|
||||||
class TablePanelCtrl extends MetricsPanelCtrl {
|
class TablePanelCtrl extends MetricsPanelCtrl {
|
||||||
@@ -16,6 +17,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
dataRaw: any;
|
dataRaw: any;
|
||||||
table: any;
|
table: any;
|
||||||
|
renderer: any;
|
||||||
|
|
||||||
panelDefaults = {
|
panelDefaults = {
|
||||||
targets: [{}],
|
targets: [{}],
|
||||||
@@ -26,11 +28,13 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
{
|
{
|
||||||
type: 'date',
|
type: 'date',
|
||||||
pattern: 'Time',
|
pattern: 'Time',
|
||||||
|
alias: 'Time',
|
||||||
dateFormat: 'YYYY-MM-DD HH:mm:ss',
|
dateFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
unit: 'short',
|
unit: 'short',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
alias: '',
|
||||||
decimals: 2,
|
decimals: 2,
|
||||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||||
colorMode: null,
|
colorMode: null,
|
||||||
@@ -42,7 +46,6 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
scroll: true,
|
scroll: true,
|
||||||
fontSize: '100%',
|
fontSize: '100%',
|
||||||
sort: {col: 0, desc: true},
|
sort: {col: 0, desc: true},
|
||||||
filterNull: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
@@ -68,6 +71,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
|
|
||||||
onInitEditMode() {
|
onInitEditMode() {
|
||||||
this.addEditorTab('Options', tablePanelEditor, 2);
|
this.addEditorTab('Options', tablePanelEditor, 2);
|
||||||
|
this.addEditorTab('Column Styles', columnOptionsTab, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
onInitPanelActions(actions) {
|
onInitPanelActions(actions) {
|
||||||
@@ -118,6 +122,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
render() {
|
render() {
|
||||||
this.table = transformDataToTable(this.dataRaw, this.panel);
|
this.table = transformDataToTable(this.dataRaw, this.panel);
|
||||||
this.table.sort(this.panel.sort);
|
this.table.sort(this.panel.sort);
|
||||||
|
|
||||||
|
this.renderer = new TableRenderer(this.panel, this.table, this.dashboard.isTimezoneUtc(), this.$sanitize);
|
||||||
|
|
||||||
return super.render(this.table);
|
return super.render(this.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,8 +148,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exportCsv() {
|
exportCsv() {
|
||||||
var renderer = new TableRenderer(this.panel, this.table, this.dashboard.isTimezoneUtc(), this.$sanitize);
|
FileExport.exportTableDataToCsv(this.renderer.render_values());
|
||||||
FileExport.exportTableDataToCsv(renderer.render_values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
link(scope, elem, attrs, ctrl) {
|
link(scope, elem, attrs, ctrl) {
|
||||||
@@ -162,9 +168,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendTableRows(tbodyElem) {
|
function appendTableRows(tbodyElem) {
|
||||||
var renderer = new TableRenderer(panel, data, ctrl.dashboard.isTimezoneUtc(), ctrl.$sanitize);
|
ctrl.renderer.setTable(data);
|
||||||
tbodyElem.empty();
|
tbodyElem.empty();
|
||||||
tbodyElem.html(renderer.render(ctrl.pageIndex));
|
tbodyElem.html(ctrl.renderer.render(ctrl.pageIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchPage(e) {
|
function switchPage(e) {
|
||||||
|
|||||||
@@ -5,12 +5,44 @@ import moment from 'moment';
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
|
||||||
export class TableRenderer {
|
export class TableRenderer {
|
||||||
formaters: any[];
|
formatters: any[];
|
||||||
colorState: any;
|
colorState: any;
|
||||||
|
|
||||||
constructor(private panel, private table, private isUtc, private sanitize) {
|
constructor(private panel, private table, private isUtc, private sanitize) {
|
||||||
this.formaters = [];
|
this.initColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTable(table) {
|
||||||
|
this.table = table;
|
||||||
|
|
||||||
|
this.initColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
initColumns() {
|
||||||
|
this.formatters = [];
|
||||||
this.colorState = {};
|
this.colorState = {};
|
||||||
|
|
||||||
|
for (let colIndex = 0; colIndex < this.table.columns.length; colIndex++) {
|
||||||
|
let column = this.table.columns[colIndex];
|
||||||
|
column.title = column.text;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.panel.styles.length; i++) {
|
||||||
|
let style = this.panel.styles[i];
|
||||||
|
|
||||||
|
var regex = kbn.stringToJsRegex(style.pattern);
|
||||||
|
if (column.text.match(regex)) {
|
||||||
|
column.style = style;
|
||||||
|
|
||||||
|
if (style.alias) {
|
||||||
|
column.title = column.text.replace(regex, style.alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formatters[colIndex] = this.createColumnFormatter(column);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getColorForValue(value, style) {
|
getColorForValue(value, style) {
|
||||||
@@ -24,7 +56,7 @@ export class TableRenderer {
|
|||||||
return _.first(style.colors);
|
return _.first(style.colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultCellFormater(v, style) {
|
defaultCellFormatter(v, style) {
|
||||||
if (v === null || v === void 0 || v === undefined) {
|
if (v === null || v === void 0 || v === undefined) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -40,18 +72,18 @@ export class TableRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createColumnFormater(style, column) {
|
createColumnFormatter(column) {
|
||||||
if (!style) {
|
if (!column.style) {
|
||||||
return this.defaultCellFormater;
|
return this.defaultCellFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.type === 'hidden') {
|
if (column.style.type === 'hidden') {
|
||||||
return v => {
|
return v => {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.type === 'date') {
|
if (column.style.type === 'date') {
|
||||||
return v => {
|
return v => {
|
||||||
if (v === undefined || v === null) {
|
if (v === undefined || v === null) {
|
||||||
return '-';
|
return '-';
|
||||||
@@ -62,12 +94,12 @@ export class TableRenderer {
|
|||||||
if (this.isUtc) {
|
if (this.isUtc) {
|
||||||
date = date.utc();
|
date = date.utc();
|
||||||
}
|
}
|
||||||
return date.format(style.dateFormat);
|
return date.format(column.style.dateFormat);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.type === 'number') {
|
if (column.style.type === 'number') {
|
||||||
let valueFormater = kbn.valueFormats[column.unit || style.unit];
|
let valueFormatter = kbn.valueFormats[column.unit || column.style.unit];
|
||||||
|
|
||||||
return v => {
|
return v => {
|
||||||
if (v === null || v === void 0) {
|
if (v === null || v === void 0) {
|
||||||
@@ -75,39 +107,24 @@ export class TableRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_.isString(v)) {
|
if (_.isString(v)) {
|
||||||
return this.defaultCellFormater(v, style);
|
return this.defaultCellFormatter(v, column.style);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.colorMode) {
|
if (column.style.colorMode) {
|
||||||
this.colorState[style.colorMode] = this.getColorForValue(v, style);
|
this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style);
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueFormater(v, style.decimals, null);
|
return valueFormatter(v, column.style.decimals, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (value) => {
|
return (value) => {
|
||||||
return this.defaultCellFormater(value, style);
|
return this.defaultCellFormatter(value, column.style);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
formatColumnValue(colIndex, value) {
|
formatColumnValue(colIndex, value) {
|
||||||
if (this.formaters[colIndex]) {
|
return this.formatters[colIndex] ? this.formatters[colIndex](value) : value;
|
||||||
return this.formaters[colIndex](value);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.panel.styles.length; i++) {
|
|
||||||
let style = this.panel.styles[i];
|
|
||||||
let column = this.table.columns[colIndex];
|
|
||||||
var regex = kbn.stringToJsRegex(style.pattern);
|
|
||||||
if (column.text.match(regex)) {
|
|
||||||
this.formaters[colIndex] = this.createColumnFormater(style, column);
|
|
||||||
return this.formaters[colIndex](value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formaters[colIndex] = this.defaultCellFormater;
|
|
||||||
return this.formaters[colIndex](value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCell(columnIndex, value, addWidthHack = false) {
|
renderCell(columnIndex, value, addWidthHack = false) {
|
||||||
@@ -126,7 +143,7 @@ export class TableRenderer {
|
|||||||
// this hack adds header content to cell (not visible)
|
// this hack adds header content to cell (not visible)
|
||||||
var widthHack = '';
|
var widthHack = '';
|
||||||
if (addWidthHack) {
|
if (addWidthHack) {
|
||||||
widthHack = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].text + '</div>';
|
widthHack = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].title + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ describe('when rendering table', () => {
|
|||||||
{
|
{
|
||||||
pattern: 'Time',
|
pattern: 'Time',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
format: 'LLL'
|
format: 'LLL',
|
||||||
|
alias: 'Timestamp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'Value',
|
pattern: '/(Val)ue/',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
unit: 'ms',
|
unit: 'ms',
|
||||||
decimals: 3,
|
decimals: 3,
|
||||||
|
alias: '$1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'Colored',
|
pattern: 'Colored',
|
||||||
@@ -132,6 +134,18 @@ describe('when rendering table', () => {
|
|||||||
var html = renderer.renderCell(6, 'text <a href="http://google.com">link</a>');
|
var html = renderer.renderCell(6, 'text <a href="http://google.com">link</a>');
|
||||||
expect(html).to.be('<td>sanitized</td>');
|
expect(html).to.be('<td>sanitized</td>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Time column title should be Timestamp', () => {
|
||||||
|
expect(table.columns[0].title).to.be('Timestamp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Value column title should be Val', () => {
|
||||||
|
expect(table.columns[1].title).to.be('Val');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Colored column title should be Colored', () => {
|
||||||
|
expect(table.columns[2].title).to.be('Colored');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -220,8 +220,7 @@ transformers['json'] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function transformDataToTable(data, panel) {
|
function transformDataToTable(data, panel) {
|
||||||
var model = new TableModel(),
|
var model = new TableModel();
|
||||||
copyData = angular.copy(data);
|
|
||||||
|
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
return model;
|
return model;
|
||||||
@@ -229,16 +228,10 @@ function transformDataToTable(data, panel) {
|
|||||||
|
|
||||||
var transformer = transformers[panel.transform];
|
var transformer = transformers[panel.transform];
|
||||||
if (!transformer) {
|
if (!transformer) {
|
||||||
throw {message: 'Transformer ' + panel.transformer + ' not found'};
|
throw {message: 'Transformer ' + panel.transform + ' not found'};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panel.filterNull) {
|
transformer.transform(data, panel, model);
|
||||||
for (var i = 0; i < copyData.length; i++) {
|
|
||||||
copyData[i].datapoints = copyData[i].datapoints.filter((dp) => dp[0] != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transformer.transform(copyData, panel, model);
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,3 +68,12 @@
|
|||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-tabs-wrapper {
|
||||||
|
@include brand-bottom-border();
|
||||||
|
@include clearfix();
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-tabs-content {
|
||||||
|
padding: $spacer*2 $spacer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-sidemenu-aside {
|
.edit-sidemenu-aside {
|
||||||
min-width: 15rem;
|
min-width: 6rem;
|
||||||
|
margin-right: $spacer*2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-sidemenu {
|
.edit-sidemenu {
|
||||||
|
|||||||
Reference in New Issue
Block a user