mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'grafana/master' into table-reducer
* grafana/master: (107 commits) another change that didn't come with earlier commit change that didn't come with in last commit reversed dashboard-padding Update CloudWatch metrics/dimension list (#16102) brought back dashboard-padding and panel-padding variables, made dashboard-padding more specific fix(prometheus): Change aligment of range queries (#16110) Minor refactoring of testdata query order PR #16122 cleaner version maintain query order Update PLUGIN_DEV.md Merge with master, and updated logo and name update table data model fix(graphite): nonNegativeDerivative argument hidden if 0, fixes #12488 Correct table names of sql storage for remotecache more fixes to snapshot more fixes to snapshot Fixed gofmt issue in PR #16093 removed empty space in snapshot fix: Update snapshot related to new jest version fixed snapshot for test ...
This commit is contained in:
@@ -27,3 +27,8 @@ If you think we missed exposing a crucial lib or Grafana component let us know b
|
||||
The angular directive `<spectrum-picker>` is now deprecated (will still work for a version more) but we recommend plugin authors
|
||||
to upgrade to new `<color-picker color="ctrl.color" onChange="ctrl.onSparklineColorChange"></color-picker>`
|
||||
|
||||
## Changes in v6.0
|
||||
|
||||
### DashboardSrv.ts
|
||||
|
||||
If you utilize [DashboardSrv](https://github.com/grafana/grafana/commit/8574dca081002f36e482b572517d8f05fd44453f#diff-1ab99561f9f6a10e1fafcddc39bc1d65) in your plugin code, `dash` was renamed to `dashboard`
|
||||
|
||||
@@ -19,22 +19,22 @@ Grafana includes built-in support for Prometheus.
|
||||
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`.
|
||||
3. Click the `+ Add data source` button in the top header.
|
||||
4. Select `Prometheus` from the *Type* dropdown.
|
||||
4. Select `Prometheus` from the _Type_ dropdown.
|
||||
|
||||
> NOTE: If you're not seeing the `Data Sources` link in your side menu it means that your current user does not have the `Admin` role for the current organization.
|
||||
|
||||
## Data source options
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
*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.
|
||||
*Url* | The http protocol, ip and port of you Prometheus server (default port is usually 9090)
|
||||
*Access* | Server (default) = URL needs to be accessible from the Grafana backend/server, Browser = URL needs to be accessible from the browser.
|
||||
*Basic Auth* | Enable basic authentication to the Prometheus data source.
|
||||
*User* | Name of your Prometheus user
|
||||
*Password* | Database user's password
|
||||
*Scrape interval* | This will be used as a lower limit for the Prometheus step query parameter. Default value is 15s.
|
||||
| Name | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| _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. |
|
||||
| _Url_ | The http protocol, ip and port of you Prometheus server (default port is usually 9090) |
|
||||
| _Access_ | Server (default) = URL needs to be accessible from the Grafana backend/server, Browser = URL needs to be accessible from the browser. |
|
||||
| _Basic Auth_ | Enable basic authentication to the Prometheus data source. |
|
||||
| _User_ | Name of your Prometheus user |
|
||||
| _Password_ | Database user's password |
|
||||
| _Scrape interval_ | This will be used as a lower limit for the Prometheus step query parameter. Default value is 15s. |
|
||||
|
||||
## Query editor
|
||||
|
||||
@@ -43,14 +43,17 @@ Open a graph in edit mode by click the title > Edit (or by pressing `e` key whil
|
||||
{{< docs-imagebox img="/img/docs/v45/prometheus_query_editor_still.png"
|
||||
animated-gif="/img/docs/v45/prometheus_query_editor.gif" >}}
|
||||
|
||||
Name | Description
|
||||
------- | --------
|
||||
*Query expression* | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/).
|
||||
*Legend format* | Controls the name of the time series, using name or pattern. For example `{{hostname}}` will be replaced with label value for the label `hostname`.
|
||||
*Min step* | Set a lower limit for the Prometheus step option. Step controls how big the jumps are when the Prometheus query engine performs range queries. Sadly there is no official prometheus documentation to link to for this very important option.
|
||||
*Resolution* | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels.
|
||||
*Metric lookup* | Search for metric names in this input field.
|
||||
*Format as* | Switch between Table, Time series or Heatmap. Table format will only work in the Table panel. Heatmap format is suitable for displaying metrics having histogram type on Heatmap panel. Under the hood, it converts cumulative histogram to regular and sorts series by the bucket bound.
|
||||
| Name | Description |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| _Query expression_ | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
|
||||
| _Legend format_ | Controls the name of the time series, using name or pattern. For example `{{hostname}}` will be replaced with label value for the label `hostname`. |
|
||||
| _Min step_ | Set a lower limit for the Prometheus step option. Step controls how big the jumps are when the Prometheus query engine performs range queries. Sadly there is no official prometheus documentation to link to for this very important option. |
|
||||
| _Resolution_ | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels. |
|
||||
| _Metric lookup_ | Search for metric names in this input field. |
|
||||
| _Format as_ | Switch between Table, Time series or Heatmap. Table format will only work in the Table panel. Heatmap format is suitable for displaying metrics having histogram type on Heatmap panel. Under the hood, it converts cumulative histogram to regular and sorts series by the bucket bound. |
|
||||
|
||||
> NOTE: Grafana slightly modifies the request dates for queries to align them with the dynamically calculated step.
|
||||
> This ensures consistent display of metrics data but can result in a small gap of data at the right edge of a graph.
|
||||
|
||||
## Templating
|
||||
|
||||
@@ -63,19 +66,18 @@ types of template variables.
|
||||
|
||||
### Query variable
|
||||
|
||||
Variable of the type *Query* allows you to query Prometheus for a list of metrics, labels or label values. The Prometheus data source plugin
|
||||
Variable of the type _Query_ allows you to query Prometheus for a list of metrics, labels or label values. The Prometheus data source plugin
|
||||
provides the following functions you can use in the `Query` input field.
|
||||
|
||||
Name | Description
|
||||
---- | --------
|
||||
*label_names()* | Returns a list of label names.
|
||||
*label_values(label)* | Returns a list of label values for the `label` in every metric.
|
||||
*label_values(metric, label)* | Returns a list of label values for the `label` in the specified metric.
|
||||
*metrics(metric)* | Returns a list of metrics matching the specified `metric` regex.
|
||||
*query_result(query)* | Returns a list of Prometheus query result for the `query`.
|
||||
|
||||
For details of *metric names*, *label names* and *label values* are please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
||||
| Name | Description |
|
||||
| ----------------------------- | ----------------------------------------------------------------------- |
|
||||
| _label_names()_ | Returns a list of label names. |
|
||||
| _label_values(label)_ | Returns a list of label values for the `label` in every metric. |
|
||||
| _label_values(metric, label)_ | Returns a list of label values for the `label` in the specified metric. |
|
||||
| _metrics(metric)_ | Returns a list of metrics matching the specified `metric` regex. |
|
||||
| _query_result(query)_ | Returns a list of Prometheus query result for the `query`. |
|
||||
|
||||
For details of _metric names_, _label names_ and _label values_ are please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
||||
|
||||
#### Using interval and range variables
|
||||
|
||||
@@ -106,10 +108,10 @@ Regex:
|
||||
|
||||
There are two syntaxes:
|
||||
|
||||
- `$<varname>` Example: rate(http_requests_total{job=~"$job"}[5m])
|
||||
- `$<varname>` Example: rate(http_requests_total{job=~"\$job"}[5m])
|
||||
- `[[varname]]` Example: rate(http_requests_total{job=~"[[job]]"}[5m])
|
||||
|
||||
Why two ways? The first syntax is easier to read and write but does not allow you to use a variable in the middle of a word. When the *Multi-value* or *Include all value*
|
||||
Why two ways? The first syntax is easier to read and write but does not allow you to use a variable in the middle of a word. When the _Multi-value_ or _Include all value_
|
||||
options are enabled, Grafana converts the labels from plain text to a regex compatible string. Which means you have to use `=~` instead of `=`.
|
||||
|
||||
## Annotations
|
||||
|
||||
79
package.json
79
package.json
@@ -11,11 +11,11 @@
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.1.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
|
||||
"@types/angular": "^1.6.6",
|
||||
"@types/chalk": "^2.2.0",
|
||||
@@ -24,26 +24,28 @@
|
||||
"@types/d3": "^4.10.1",
|
||||
"@types/enzyme": "^3.1.13",
|
||||
"@types/inquirer": "^0.0.43",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/jest": "^24.0.11",
|
||||
"@types/jquery": "^1.10.35",
|
||||
"@types/node": "^8.0.31",
|
||||
"@types/papaparse": "^4.5.9",
|
||||
"@types/react": "^16.8.8",
|
||||
"@types/react-dom": "^16.8.2",
|
||||
"@types/react-grid-layout": "^0.16.6",
|
||||
"@types/react-select": "^2.0.4",
|
||||
"@types/react-transition-group": "^2.0.15",
|
||||
"@types/react-virtualized": "^9.18.12",
|
||||
"@types/clipboard": "^2.0.1",
|
||||
"angular-mocks": "1.6.6",
|
||||
"autoprefixer": "^6.4.0",
|
||||
"axios": "^0.17.1",
|
||||
"autoprefixer": "^9.4.10",
|
||||
"axios": "^0.18.0",
|
||||
"babel-core": "^7.0.0-bridge",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-jest": "^24.5.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-angularjs-annotate": "^0.9.0",
|
||||
"babel-plugin-angularjs-annotate": "^0.10.0",
|
||||
"chalk": "^2.4.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"clean-webpack-plugin": "^2.0.0",
|
||||
"concurrently": "^4.1.0",
|
||||
"css-loader": "^0.28.7",
|
||||
"css-loader": "^2.1.1",
|
||||
"enzyme": "^3.6.0",
|
||||
"enzyme-adapter-react-16": "^1.5.0",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
@@ -51,11 +53,11 @@
|
||||
"es6-shim": "^0.35.3",
|
||||
"execa": "^1.0.0",
|
||||
"expect.js": "~0.2.0",
|
||||
"expose-loader": "^0.7.3",
|
||||
"file-loader": "^1.1.11",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||
"expose-loader": "0.7.5",
|
||||
"file-loader": "^3.0.1",
|
||||
"fork-ts-checker-webpack-plugin": "^1.0.0",
|
||||
"gaze": "^1.1.2",
|
||||
"glob": "~7.0.0",
|
||||
"glob": "~7.1.3",
|
||||
"grunt": "1.0.1",
|
||||
"grunt-angular-templates": "^1.1.0",
|
||||
"grunt-cli": "~1.2.0",
|
||||
@@ -69,29 +71,29 @@
|
||||
"grunt-sass-lint": "^0.2.4",
|
||||
"grunt-usemin": "3.1.1",
|
||||
"grunt-webpack": "^3.0.2",
|
||||
"html-loader": "^0.5.1",
|
||||
"html-webpack-harddisk-plugin": "^0.2.0",
|
||||
"html-loader": "0.5.5",
|
||||
"html-webpack-harddisk-plugin": "^1.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^1.3.1",
|
||||
"inquirer": "^6.2.2",
|
||||
"jest": "^23.6.0",
|
||||
"jest": "^24.5.0",
|
||||
"jest-date-mock": "^1.0.6",
|
||||
"lint-staged": "^8.1.3",
|
||||
"load-grunt-tasks": "3.5.2",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"mocha": "^4.0.1",
|
||||
"monaco-editor": "^0.15.6",
|
||||
"ng-annotate-loader": "^0.6.1",
|
||||
"ng-annotate-webpack-plugin": "^0.3.0",
|
||||
"ngtemplate-loader": "^2.0.1",
|
||||
"node-sass": "^4.11.0",
|
||||
"npm": "^5.4.2",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||
"ora": "^3.1.0",
|
||||
"npm": "^6.9.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"ora": "^3.2.0",
|
||||
"phantomjs-prebuilt": "^2.1.15",
|
||||
"postcss-browser-reporter": "^0.5.0",
|
||||
"postcss-loader": "^2.0.6",
|
||||
"postcss-reporter": "^5.0.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-reporter": "^6.0.1",
|
||||
"prettier": "1.16.4",
|
||||
"react-hot-loader": "^4.3.6",
|
||||
"react-test-renderer": "^16.5.0",
|
||||
@@ -99,27 +101,27 @@
|
||||
"regexp-replace-loader": "^1.0.1",
|
||||
"rimraf": "^2.6.3",
|
||||
"sass-lint": "^1.10.2",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sass-loader": "7.1.0",
|
||||
"semver": "^5.6.0",
|
||||
"sinon": "1.17.6",
|
||||
"style-loader": "^0.21.0",
|
||||
"style-loader": "0.23.1",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "^0.1.36",
|
||||
"ts-jest": "^23.10.4",
|
||||
"ts-loader": "^5.1.0",
|
||||
"ts-node": "^8.0.2",
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.8.0",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-jest": "^24.0.0",
|
||||
"ts-loader": "5.3.3",
|
||||
"ts-node": "8.0.2",
|
||||
"tslib": "1.9.3",
|
||||
"tslint": "5.14.0",
|
||||
"tslint-loader": "^3.5.3",
|
||||
"tslint-react": "^3.6.0",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"webpack": "4.19.1",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"typescript": "3.3.3333",
|
||||
"webpack": "4.29.6",
|
||||
"webpack-bundle-analyzer": "3.1.0",
|
||||
"webpack-cleanup-plugin": "^0.5.1",
|
||||
"webpack-cli": "^2.1.4",
|
||||
"webpack-dev-server": "^3.1.0",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"webpack-cli": "3.2.3",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-merge": "4.2.1",
|
||||
"zone.js": "^0.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -176,7 +178,7 @@
|
||||
"baron": "^3.0.3",
|
||||
"brace": "^0.10.0",
|
||||
"classnames": "^2.2.6",
|
||||
"clipboard": "^1.7.1",
|
||||
"clipboard": "^2.0.4",
|
||||
"d3": "^4.11.0",
|
||||
"d3-scale-chromatic": "^1.3.0",
|
||||
"eventemitter3": "^2.0.3",
|
||||
@@ -188,6 +190,7 @@
|
||||
"mousetrap": "^1.6.0",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
"nodemon": "^1.18.10",
|
||||
"papaparse": "^4.6.3",
|
||||
"prismjs": "^1.6.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"rc-cascader": "^0.14.0",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@torkelo/react-select": "2.1.1",
|
||||
"@types/react-color": "^2.14.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3": "^5.7.0",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.10",
|
||||
"moment": "^2.22.2",
|
||||
@@ -43,6 +44,7 @@
|
||||
"@storybook/addon-knobs": "^4.1.7",
|
||||
"@storybook/react": "^4.1.4",
|
||||
"@types/classnames": "^2.2.6",
|
||||
"@types/d3": "^5.7.0",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/jquery": "^1.10.35",
|
||||
"@types/lodash": "^4.14.119",
|
||||
|
||||
@@ -45,7 +45,7 @@ $arrowSize: 15px;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
top: 0;
|
||||
left: calc(100% -$arrowSize);
|
||||
left: calc(100%-#{$arrowSize});
|
||||
}
|
||||
|
||||
&[data-placement^='right'] {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { FormField, Props } from './FormField';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const setup = (propOverrides?: Partial<Props>) => {
|
||||
const props: Props = {
|
||||
label: 'Test',
|
||||
labelWidth: 11,
|
||||
@@ -15,10 +15,23 @@ const setup = (propOverrides?: object) => {
|
||||
return shallow(<FormField {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
describe('FormField', () => {
|
||||
it('should render component with default inputEl', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render component with custom inputEl', () => {
|
||||
const wrapper = setup({
|
||||
inputEl: (
|
||||
<>
|
||||
<span>Input</span>
|
||||
<button>Ok</button>
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
labelWidth?: number;
|
||||
inputWidth?: number;
|
||||
inputEl?: React.ReactNode;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
@@ -12,14 +13,18 @@ const defaultProps = {
|
||||
inputWidth: 12,
|
||||
};
|
||||
|
||||
const FormField: FunctionComponent<Props> = ({ label, labelWidth, inputWidth, ...inputProps }) => {
|
||||
/**
|
||||
* Default form field including label used in Grafana UI. Default input element is simple <input />. You can also pass
|
||||
* custom inputEl if required in which case inputWidth and inputProps are ignored.
|
||||
*/
|
||||
export const FormField: FunctionComponent<Props> = ({ label, labelWidth, inputWidth, inputEl, ...inputProps }) => {
|
||||
return (
|
||||
<div className="form-field">
|
||||
<FormLabel width={labelWidth}>{label}</FormLabel>
|
||||
<input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />
|
||||
{inputEl || <input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FormField.displayName = 'FormField';
|
||||
FormField.defaultProps = defaultProps;
|
||||
export { FormField };
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
exports[`FormField should render component with custom inputEl 1`] = `
|
||||
<div
|
||||
className="form-field"
|
||||
>
|
||||
<Component
|
||||
width={11}
|
||||
>
|
||||
Test
|
||||
</Component>
|
||||
<span>
|
||||
Input
|
||||
</span>
|
||||
<button>
|
||||
Ok
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FormField should render component with default inputEl 1`] = `
|
||||
<div
|
||||
className="form-field"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number, text, object } from '@storybook/addon-knobs';
|
||||
import { PieChart, PieChartType } from './PieChart';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
datapoints: object('datapoints', [
|
||||
{
|
||||
value: 100,
|
||||
name: '100',
|
||||
color: '#7EB26D',
|
||||
},
|
||||
{
|
||||
value: 200,
|
||||
name: '200',
|
||||
color: '#6ED0E0',
|
||||
},
|
||||
]),
|
||||
pieType: text('pieType', PieChartType.PIE),
|
||||
strokeWidth: number('strokeWidth', 1),
|
||||
unit: text('unit', 'ms'),
|
||||
};
|
||||
};
|
||||
|
||||
const PieChartStories = storiesOf('UI/PieChart/PieChart', module);
|
||||
|
||||
PieChartStories.addDecorator(withCenteredStory);
|
||||
|
||||
PieChartStories.add('Pie type: pie', () => {
|
||||
const { datapoints, pieType, strokeWidth, unit } = getKnobs();
|
||||
|
||||
return renderComponentWithTheme(PieChart, {
|
||||
width: 200,
|
||||
height: 400,
|
||||
datapoints,
|
||||
pieType,
|
||||
strokeWidth,
|
||||
unit,
|
||||
});
|
||||
});
|
||||
147
packages/grafana-ui/src/components/PieChart/PieChart.tsx
Normal file
147
packages/grafana-ui/src/components/PieChart/PieChart.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { select, pie, arc, event } from 'd3';
|
||||
import { sum } from 'lodash';
|
||||
|
||||
import { GrafanaThemeType } from '../../types';
|
||||
import { Themeable } from '../../index';
|
||||
|
||||
export enum PieChartType {
|
||||
PIE = 'pie',
|
||||
DONUT = 'donut',
|
||||
}
|
||||
|
||||
export interface PieChartDataPoint {
|
||||
value: number;
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface Props extends Themeable {
|
||||
height: number;
|
||||
width: number;
|
||||
datapoints: PieChartDataPoint[];
|
||||
|
||||
unit: string;
|
||||
pieType: PieChartType;
|
||||
strokeWidth: number;
|
||||
}
|
||||
|
||||
export class PieChart extends PureComponent<Props> {
|
||||
containerElement: any;
|
||||
svgElement: any;
|
||||
tooltipElement: any;
|
||||
tooltipValueElement: any;
|
||||
|
||||
static defaultProps = {
|
||||
pieType: 'pie',
|
||||
format: 'short',
|
||||
stat: 'current',
|
||||
strokeWidth: 1,
|
||||
theme: GrafanaThemeType.Dark,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { datapoints, pieType, strokeWidth } = this.props;
|
||||
|
||||
if (datapoints.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = datapoints.map(datapoint => datapoint.value);
|
||||
const names = datapoints.map(datapoint => datapoint.name);
|
||||
const colors = datapoints.map(datapoint => datapoint.color);
|
||||
|
||||
const total = sum(data) || 1;
|
||||
const percents = data.map((item: number) => (item / total) * 100);
|
||||
|
||||
const width = this.containerElement.offsetWidth;
|
||||
const height = this.containerElement.offsetHeight;
|
||||
const radius = Math.min(width, height) / 2;
|
||||
|
||||
const outerRadius = radius - radius / 10;
|
||||
const innerRadius = pieType === PieChartType.PIE ? 0 : radius - radius / 3;
|
||||
|
||||
const svg = select(this.svgElement)
|
||||
.html('')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${width / 2},${height / 2})`);
|
||||
|
||||
const pieChart = pie();
|
||||
|
||||
const customArc = arc()
|
||||
.outerRadius(outerRadius)
|
||||
.innerRadius(innerRadius)
|
||||
.padAngle(0);
|
||||
|
||||
svg
|
||||
.selectAll('path')
|
||||
.data(pieChart(data))
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', customArc as any)
|
||||
.attr('fill', (d: any, idx: number) => colors[idx])
|
||||
.style('fill-opacity', 0.15)
|
||||
.style('stroke', (d: any, idx: number) => colors[idx])
|
||||
.style('stroke-width', `${strokeWidth}px`)
|
||||
.on('mouseover', (d: any, idx: any) => {
|
||||
select(this.tooltipElement).style('opacity', 1);
|
||||
select(this.tooltipValueElement).text(`${names[idx]} (${percents[idx].toFixed(2)}%)`);
|
||||
})
|
||||
.on('mousemove', () => {
|
||||
select(this.tooltipElement)
|
||||
.style('top', `${event.pageY - height / 2}px`)
|
||||
.style('left', `${event.pageX}px`);
|
||||
})
|
||||
.on('mouseout', () => {
|
||||
select(this.tooltipElement).style('opacity', 0);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { height, width, datapoints } = this.props;
|
||||
|
||||
if (datapoints.length > 0) {
|
||||
return (
|
||||
<div className="piechart-panel">
|
||||
<div
|
||||
ref={element => (this.containerElement = element)}
|
||||
className="piechart-container"
|
||||
style={{
|
||||
height: `${height * 0.9}px`,
|
||||
width: `${Math.min(width, height * 1.3)}px`,
|
||||
}}
|
||||
>
|
||||
<svg ref={element => (this.svgElement = element)} />
|
||||
</div>
|
||||
<div className="piechart-tooltip" ref={element => (this.tooltipElement = element)}>
|
||||
<div className="piechart-tooltip-time">
|
||||
<div
|
||||
id="tooltip-value"
|
||||
className="piechart-tooltip-value"
|
||||
ref={element => (this.tooltipValueElement = element)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="piechart-panel">
|
||||
<div className="datapoints-warning">
|
||||
<span className="small">No data points</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
|
||||
import { SecretFormField } from './SecretFormField';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
|
||||
const SecretFormFieldStories = storiesOf('UI/SecretFormField/SecretFormField', module);
|
||||
|
||||
SecretFormFieldStories.addDecorator(withCenteredStory);
|
||||
const getSecretFormFieldKnobs = () => {
|
||||
return {
|
||||
isConfigured: boolean('Set configured state', false),
|
||||
};
|
||||
};
|
||||
|
||||
SecretFormFieldStories.add('default', () => {
|
||||
const knobs = getSecretFormFieldKnobs();
|
||||
return (
|
||||
<UseState initialState="Input value">
|
||||
{(value, setValue) => (
|
||||
<SecretFormField
|
||||
label={'Secret field'}
|
||||
labelWidth={10}
|
||||
value={value}
|
||||
isConfigured={knobs.isConfigured}
|
||||
onChange={e => setValue(e.currentTarget.value)}
|
||||
onReset={() => {
|
||||
action('Value was reset')('');
|
||||
setValue('');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</UseState>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { omit } from 'lodash';
|
||||
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
|
||||
import { FormField } from '..';
|
||||
|
||||
interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||
// Function to use when reset is clicked. Means you have to reset the input value yourself as this is uncontrolled
|
||||
// component (or do something else if required).
|
||||
onReset: () => void;
|
||||
isConfigured: boolean;
|
||||
|
||||
label?: string;
|
||||
labelWidth?: number;
|
||||
inputWidth?: number;
|
||||
// Placeholder of the input field when in non configured state.
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
inputWidth: 12,
|
||||
placeholder: 'Password',
|
||||
label: 'Password',
|
||||
};
|
||||
|
||||
/**
|
||||
* Form field that has 2 states configured and not configured. If configured it will not show its contents and adds
|
||||
* a reset button that will clear the input and makes it accessible. In non configured state it behaves like normal
|
||||
* form field. This is used for passwords or anything that is encrypted on the server and is later returned encrypted
|
||||
* to the user (like datasource passwords).
|
||||
*/
|
||||
export const SecretFormField: FunctionComponent<Props> = ({
|
||||
label,
|
||||
labelWidth,
|
||||
inputWidth,
|
||||
onReset,
|
||||
isConfigured,
|
||||
placeholder,
|
||||
...inputProps
|
||||
}: Props) => {
|
||||
return (
|
||||
<FormField
|
||||
label={label!}
|
||||
labelWidth={labelWidth}
|
||||
inputEl={
|
||||
isConfigured ? (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
className={`gf-form-input width-${inputWidth! - 2}`}
|
||||
disabled={true}
|
||||
value="configured"
|
||||
{...omit(inputProps, 'value')}
|
||||
/>
|
||||
<button className="btn btn-secondary gf-form-btn" onClick={onReset}>
|
||||
reset
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<input
|
||||
type="password"
|
||||
className={`gf-form-input width-${inputWidth}`}
|
||||
placeholder={placeholder}
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
SecretFormField.defaultProps = defaultProps;
|
||||
SecretFormField.displayName = 'SecretFormField';
|
||||
@@ -40,8 +40,6 @@ export function makeDummyTable(columnCount: number, rowCount: number): TableData
|
||||
const suffix = (rowId + 1).toString();
|
||||
return Array.from(new Array(columnCount), (x, colId) => columnIndexToLeter(colId) + suffix);
|
||||
}),
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CellMeasurerCache,
|
||||
CellMeasurer,
|
||||
GridCellProps,
|
||||
Index,
|
||||
} from 'react-virtualized';
|
||||
import { Themeable } from '../../types/theme';
|
||||
|
||||
@@ -26,6 +27,7 @@ import { stringToJsRegex } from '../../utils/index';
|
||||
export interface Props extends Themeable {
|
||||
data: TableData;
|
||||
|
||||
minColumnWidth: number;
|
||||
showHeader: boolean;
|
||||
fixedHeader: boolean;
|
||||
fixedColumns: number;
|
||||
@@ -46,6 +48,7 @@ interface State {
|
||||
|
||||
interface ColumnRenderInfo {
|
||||
header: string;
|
||||
width: number;
|
||||
builder: TableCellBuilder;
|
||||
}
|
||||
|
||||
@@ -64,6 +67,7 @@ export class Table extends Component<Props, State> {
|
||||
fixedHeader: true,
|
||||
fixedColumns: 0,
|
||||
rotate: false,
|
||||
minColumnWidth: 150,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -76,7 +80,7 @@ export class Table extends Component<Props, State> {
|
||||
this.renderer = this.initColumns(props);
|
||||
this.measurer = new CellMeasurerCache({
|
||||
defaultHeight: 30,
|
||||
defaultWidth: 150,
|
||||
fixedWidth: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,7 +114,8 @@ export class Table extends Component<Props, State> {
|
||||
|
||||
/** Given the configuration, setup how each column gets rendered */
|
||||
initColumns(props: Props): ColumnRenderInfo[] {
|
||||
const { styles, data } = props;
|
||||
const { styles, data, width, minColumnWidth } = props;
|
||||
const columnWidth = Math.max(width / data.columns.length, minColumnWidth);
|
||||
|
||||
return data.columns.map((col, index) => {
|
||||
let title = col.text;
|
||||
@@ -131,6 +136,7 @@ export class Table extends Component<Props, State> {
|
||||
|
||||
return {
|
||||
header: title,
|
||||
width: columnWidth,
|
||||
builder: getCellBuilder(col, style, this.props),
|
||||
};
|
||||
});
|
||||
@@ -228,6 +234,10 @@ export class Table extends Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
getColumnWidth = (col: Index): number => {
|
||||
return this.renderer[col.index].width;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props;
|
||||
const { data } = this.state;
|
||||
@@ -269,7 +279,7 @@ export class Table extends Component<Props, State> {
|
||||
rowCount={rowCount}
|
||||
overscanColumnCount={8}
|
||||
overscanRowCount={8}
|
||||
columnWidth={this.measurer.columnWidth}
|
||||
columnWidth={this.getColumnWidth}
|
||||
deferredMeasurementCache={this.measurer}
|
||||
cellRenderer={this.cellRenderer}
|
||||
rowHeight={this.measurer.rowHeight}
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
@@ -195,7 +195,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"typography": Object {
|
||||
"fontFamily": Object {
|
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
"sansSerif": "'Roboto', Helvetica, Arial, sans-serif",
|
||||
"sansSerif": "'Roboto', 'Helvetica Neue', Arial, sans-serif",
|
||||
},
|
||||
"heading": Object {
|
||||
"h1": "28px",
|
||||
@@ -211,6 +211,10 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"sm": 1.1,
|
||||
"xs": 1,
|
||||
},
|
||||
"link": Object {
|
||||
"decoration": "none",
|
||||
"hoverDecoration": "none",
|
||||
},
|
||||
"size": Object {
|
||||
"base": "13px",
|
||||
"lg": "18px",
|
||||
@@ -225,6 +229,15 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"semibold": 500,
|
||||
},
|
||||
},
|
||||
"zIndex": Object {
|
||||
"dropdown": "1000",
|
||||
"modal": "1050",
|
||||
"modalBackdrop": "1040",
|
||||
"navbarFixed": "1020",
|
||||
"sidemenu": "1025",
|
||||
"tooltip": "1030",
|
||||
"typeahead": "1060",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -339,7 +352,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"typography": Object {
|
||||
"fontFamily": Object {
|
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
"sansSerif": "'Roboto', Helvetica, Arial, sans-serif",
|
||||
"sansSerif": "'Roboto', 'Helvetica Neue', Arial, sans-serif",
|
||||
},
|
||||
"heading": Object {
|
||||
"h1": "28px",
|
||||
@@ -355,6 +368,10 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"sm": 1.1,
|
||||
"xs": 1,
|
||||
},
|
||||
"link": Object {
|
||||
"decoration": "none",
|
||||
"hoverDecoration": "none",
|
||||
},
|
||||
"size": Object {
|
||||
"base": "13px",
|
||||
"lg": "18px",
|
||||
@@ -369,6 +386,15 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"semibold": 500,
|
||||
},
|
||||
},
|
||||
"zIndex": Object {
|
||||
"dropdown": "1000",
|
||||
"modal": "1050",
|
||||
"modalBackdrop": "1040",
|
||||
"navbarFixed": "1020",
|
||||
"sidemenu": "1025",
|
||||
"tooltip": "1030",
|
||||
"typeahead": "1060",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@ export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||
// Forms
|
||||
export { FormLabel } from './FormLabel/FormLabel';
|
||||
export { FormField } from './FormField/FormField';
|
||||
export { SecretFormField } from './SecretFormFied/SecretFormField';
|
||||
|
||||
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||
export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
|
||||
@@ -24,6 +25,7 @@ export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
|
||||
export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
|
||||
export { Switch } from './Switch/Switch';
|
||||
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
|
||||
export { PieChart, PieChartDataPoint, PieChartType } from './PieChart/PieChart';
|
||||
export { UnitPicker } from './UnitPicker/UnitPicker';
|
||||
export { Input, InputStatus } from './Input/Input';
|
||||
|
||||
|
||||
@@ -110,7 +110,6 @@ $font-size-h4: ${theme.typography.heading.h4} !default;
|
||||
$font-size-h5: ${theme.typography.heading.h5} !default;
|
||||
$font-size-h6: ${theme.typography.heading.h6} !default;
|
||||
|
||||
$headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$headings-line-height: ${theme.typography.lineHeight.sm} !default;
|
||||
|
||||
// Components
|
||||
@@ -130,8 +129,8 @@ $page-sidebar-margin: 56px;
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
$link-decoration: none !default;
|
||||
$link-hover-decoration: none !default;
|
||||
$link-decoration: ${theme.typography.link.decoration} !default;
|
||||
$link-hover-decoration: ${theme.typography.link.hoverDecoration} !default;
|
||||
|
||||
// Tables
|
||||
//
|
||||
@@ -166,13 +165,13 @@ $form-icon-danger: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www
|
||||
// -------------------------
|
||||
// Used for a bird's eye view of components dependent on the z-axis
|
||||
// Try to avoid customizing these :)
|
||||
$zindex-dropdown: 1000;
|
||||
$zindex-navbar-fixed: 1020;
|
||||
$zindex-sidemenu: 1025;
|
||||
$zindex-tooltip: 1030;
|
||||
$zindex-modal-backdrop: 1040;
|
||||
$zindex-modal: 1050;
|
||||
$zindex-typeahead: 1060;
|
||||
$zindex-dropdown: ${theme.zIndex.dropdown};
|
||||
$zindex-navbar-fixed: ${theme.zIndex.navbarFixed};
|
||||
$zindex-sidemenu: ${theme.zIndex.sidemenu};
|
||||
$zindex-tooltip: ${theme.zIndex.tooltip};
|
||||
$zindex-modal-backdrop: ${theme.zIndex.modalBackdrop};
|
||||
$zindex-modal: ${theme.zIndex.modal};
|
||||
$zindex-typeahead: ${theme.zIndex.typeahead};
|
||||
|
||||
// Buttons
|
||||
//
|
||||
@@ -197,10 +196,8 @@ $btn-semi-transparent: rgba(0, 0, 0, 0.2) !default;
|
||||
$side-menu-width: 60px;
|
||||
|
||||
// dashboard
|
||||
$dashboard-padding: 10px * 2;
|
||||
$panel-horizontal-padding: 10;
|
||||
$panel-vertical-padding: 5;
|
||||
$panel-padding: 0px $panel-horizontal-padding + 0px $panel-vertical-padding + 0px $panel-horizontal-padding + 0px;
|
||||
$dashboard-padding: $space-md;
|
||||
$panel-padding: 0 $space-md $space-sm $space-md;
|
||||
|
||||
// tabs
|
||||
$tabs-padding: 10px 15px 9px;
|
||||
|
||||
@@ -4,7 +4,7 @@ const theme: GrafanaThemeCommons = {
|
||||
name: 'Grafana Default',
|
||||
typography: {
|
||||
fontFamily: {
|
||||
sansSerif: "'Roboto', Helvetica, Arial, sans-serif",
|
||||
sansSerif: "'Roboto', 'Helvetica Neue', Arial, sans-serif",
|
||||
monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
},
|
||||
size: {
|
||||
@@ -34,6 +34,10 @@ const theme: GrafanaThemeCommons = {
|
||||
md: 4 / 3,
|
||||
lg: 1.5,
|
||||
},
|
||||
link: {
|
||||
decoration: 'none',
|
||||
hoverDecoration: 'none',
|
||||
},
|
||||
},
|
||||
breakpoints: {
|
||||
xs: '0',
|
||||
@@ -66,6 +70,15 @@ const theme: GrafanaThemeCommons = {
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
},
|
||||
zIndex: {
|
||||
dropdown: '1000',
|
||||
navbarFixed: '1020',
|
||||
sidemenu: '1025',
|
||||
tooltip: '1030',
|
||||
modalBackdrop: '1040',
|
||||
modal: '1050',
|
||||
typeahead: '1060',
|
||||
},
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
@@ -50,28 +50,29 @@ export enum NullValueMode {
|
||||
/** View model projection of many time series */
|
||||
export type TimeSeriesVMs = TimeSeriesVM[];
|
||||
|
||||
export enum ColumnType {
|
||||
time = 'time', // or date
|
||||
number = 'number',
|
||||
string = 'string',
|
||||
boolean = 'boolean',
|
||||
other = 'other', // Object, Array, etc
|
||||
}
|
||||
|
||||
export interface Column {
|
||||
text: string;
|
||||
title?: string;
|
||||
type?: string;
|
||||
sort?: boolean;
|
||||
desc?: boolean;
|
||||
text: string; // The column name
|
||||
type?: ColumnType;
|
||||
filterable?: boolean;
|
||||
unit?: string;
|
||||
dateFormat?: string; // Source data format
|
||||
}
|
||||
|
||||
export interface Tags {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface TableData {
|
||||
name?: string;
|
||||
columns: Column[];
|
||||
rows: any[];
|
||||
type: string;
|
||||
columnMap: any;
|
||||
}
|
||||
|
||||
export type SingleStatValue = number | string | null;
|
||||
|
||||
/*
|
||||
* So we can add meta info like tags & series name
|
||||
*/
|
||||
export interface SingleStatValueInfo {
|
||||
value: SingleStatValue;
|
||||
rows: any[][];
|
||||
tags?: Tags;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentClass } from 'react';
|
||||
import { TimeSeries, LoadingState, TableData } from './data';
|
||||
import { LoadingState, TableData } from './data';
|
||||
import { TimeRange } from './time';
|
||||
import { ScopedVars } from './datasource';
|
||||
|
||||
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
|
||||
|
||||
export interface PanelProps<T = any> {
|
||||
panelData: PanelData;
|
||||
data?: TableData[];
|
||||
timeRange: TimeRange;
|
||||
loading: LoadingState;
|
||||
options: T;
|
||||
@@ -16,11 +16,6 @@ export interface PanelProps<T = any> {
|
||||
replaceVariables: InterpolateFunction;
|
||||
}
|
||||
|
||||
export interface PanelData {
|
||||
timeSeries?: TimeSeries[];
|
||||
tableData?: TableData;
|
||||
}
|
||||
|
||||
export interface PanelEditorProps<T = any> {
|
||||
options: T;
|
||||
onOptionsChange: (options: T) => void;
|
||||
|
||||
@@ -46,6 +46,10 @@ export interface GrafanaThemeCommons {
|
||||
h5: string;
|
||||
h6: string;
|
||||
};
|
||||
link: {
|
||||
decoration: string;
|
||||
hoverDecoration: string;
|
||||
};
|
||||
};
|
||||
spacing: {
|
||||
d: string;
|
||||
@@ -71,6 +75,15 @@ export interface GrafanaThemeCommons {
|
||||
horizontal: number;
|
||||
vertical: number;
|
||||
};
|
||||
zIndex: {
|
||||
dropdown: string;
|
||||
navbarFixed: string;
|
||||
sidemenu: string;
|
||||
tooltip: string;
|
||||
modalBackdrop: string;
|
||||
modal: string;
|
||||
typeahead: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GrafanaTheme extends GrafanaThemeCommons {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
exports[`processTableData basic processing should generate a header and fix widths 1`] = `
|
||||
Object {
|
||||
"columnMap": Object {},
|
||||
"columns": Array [
|
||||
Object {
|
||||
"text": "Column 1",
|
||||
@@ -31,13 +30,11 @@ Object {
|
||||
null,
|
||||
],
|
||||
],
|
||||
"type": "table",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`processTableData basic processing should read header and two rows 1`] = `
|
||||
Object {
|
||||
"columnMap": Object {},
|
||||
"columns": Array [
|
||||
Object {
|
||||
"text": "a",
|
||||
@@ -61,6 +58,5 @@ Object {
|
||||
6,
|
||||
],
|
||||
],
|
||||
"type": "table",
|
||||
}
|
||||
`;
|
||||
|
||||
24
packages/grafana-ui/src/utils/floatPairs.test.ts
Normal file
24
packages/grafana-ui/src/utils/floatPairs.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { getFlotPairs } from './flotPairs';
|
||||
|
||||
describe('getFlotPairs', () => {
|
||||
const table = {
|
||||
rows: [[1, 100, 'a'], [2, 200, 'b'], [3, 300, 'c']],
|
||||
};
|
||||
it('should get X and y', () => {
|
||||
const pairs = getFlotPairs({ rows: table.rows, xIndex: 0, yIndex: 1 });
|
||||
|
||||
expect(pairs.length).toEqual(3);
|
||||
expect(pairs[0].length).toEqual(2);
|
||||
expect(pairs[0][0]).toEqual(1);
|
||||
expect(pairs[0][1]).toEqual(100);
|
||||
});
|
||||
|
||||
it('should work with strings', () => {
|
||||
const pairs = getFlotPairs({ rows: table.rows, xIndex: 0, yIndex: 2 });
|
||||
|
||||
expect(pairs.length).toEqual(3);
|
||||
expect(pairs[0].length).toEqual(2);
|
||||
expect(pairs[0][0]).toEqual(1);
|
||||
expect(pairs[0][1]).toEqual('a');
|
||||
});
|
||||
});
|
||||
38
packages/grafana-ui/src/utils/flotPairs.ts
Normal file
38
packages/grafana-ui/src/utils/flotPairs.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Types
|
||||
import { NullValueMode } from '../types/index';
|
||||
|
||||
export interface FloatPairsOptions {
|
||||
rows: any[][];
|
||||
xIndex: number;
|
||||
yIndex: number;
|
||||
nullValueMode?: NullValueMode;
|
||||
}
|
||||
|
||||
export function getFlotPairs({ rows, xIndex, yIndex, nullValueMode }: FloatPairsOptions): any[][] {
|
||||
const ignoreNulls = nullValueMode === NullValueMode.Ignore;
|
||||
const nullAsZero = nullValueMode === NullValueMode.AsZero;
|
||||
|
||||
const pairs: any[][] = [];
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const x = rows[i][xIndex];
|
||||
let y = rows[i][yIndex];
|
||||
|
||||
if (y === null) {
|
||||
if (ignoreNulls) {
|
||||
continue;
|
||||
}
|
||||
if (nullAsZero) {
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// X must be a value
|
||||
if (x === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pairs.push([x, y]);
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './processTimeSeries';
|
||||
export * from './singlestat';
|
||||
export * from './processTableData';
|
||||
export * from './valueFormats/valueFormats';
|
||||
export * from './colors';
|
||||
export * from './namedColorsPalette';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseCSV } from './processTableData';
|
||||
import { parseCSV, toTableData } from './processTableData';
|
||||
|
||||
describe('processTableData', () => {
|
||||
describe('basic processing', () => {
|
||||
@@ -18,3 +18,41 @@ describe('processTableData', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTableData', () => {
|
||||
it('converts timeseries to table skipping nulls', () => {
|
||||
const input1 = {
|
||||
target: 'Field Name',
|
||||
datapoints: [[100, 1], [200, 2]],
|
||||
};
|
||||
const input2 = {
|
||||
// without target
|
||||
target: '',
|
||||
datapoints: [[100, 1], [200, 2]],
|
||||
};
|
||||
const data = toTableData([null, input1, input2, null, null]);
|
||||
expect(data.length).toBe(2);
|
||||
expect(data[0].columns[0].text).toBe(input1.target);
|
||||
expect(data[0].rows).toBe(input1.datapoints);
|
||||
|
||||
// Default name
|
||||
expect(data[1].columns[0].text).toEqual('Value');
|
||||
});
|
||||
|
||||
it('keeps tableData unchanged', () => {
|
||||
const input = {
|
||||
columns: [{ text: 'A' }, { text: 'B' }, { text: 'C' }],
|
||||
rows: [[100, 'A', 1], [200, 'B', 2], [300, 'C', 3]],
|
||||
};
|
||||
const data = toTableData([null, input, null, null]);
|
||||
expect(data.length).toBe(1);
|
||||
expect(data[0]).toBe(input);
|
||||
});
|
||||
|
||||
it('supports null values OK', () => {
|
||||
expect(toTableData([null, null, null, null])).toEqual([]);
|
||||
expect(toTableData(undefined)).toEqual([]);
|
||||
expect(toTableData((null as unknown) as any[])).toEqual([]);
|
||||
expect(toTableData([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import isNumber from 'lodash/isNumber';
|
||||
import Papa, { ParseError, ParseMeta } from 'papaparse';
|
||||
|
||||
// Types
|
||||
import { TableData, Column } from '../types';
|
||||
import { TableData, Column, TimeSeries, ColumnType } from '../types';
|
||||
|
||||
// Subset of all parse options
|
||||
export interface TableParseOptions {
|
||||
@@ -70,8 +70,6 @@ export function matchRowSizes(table: TableData): TableData {
|
||||
return {
|
||||
columns,
|
||||
rows: fixedRows,
|
||||
type: table.type,
|
||||
columnMap: table.columnMap,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,8 +116,6 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||
return {
|
||||
columns: [],
|
||||
rows: [],
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,11 +126,49 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||
return matchRowSizes({
|
||||
columns: makeColumns(header),
|
||||
rows: results.data,
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
});
|
||||
}
|
||||
|
||||
function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
|
||||
return {
|
||||
name: timeSeries.target,
|
||||
columns: [
|
||||
{
|
||||
text: timeSeries.target || 'Value',
|
||||
unit: timeSeries.unit,
|
||||
},
|
||||
{
|
||||
text: 'Time',
|
||||
type: ColumnType.time,
|
||||
unit: 'dateTimeAsIso',
|
||||
},
|
||||
],
|
||||
rows: timeSeries.datapoints,
|
||||
};
|
||||
}
|
||||
|
||||
export const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
|
||||
|
||||
export const toTableData = (results?: any[]): TableData[] => {
|
||||
if (!results) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return results
|
||||
.filter(d => !!d)
|
||||
.map(data => {
|
||||
if (data.hasOwnProperty('columns')) {
|
||||
return data as TableData;
|
||||
}
|
||||
if (data.hasOwnProperty('datapoints')) {
|
||||
return convertTimeSeriesToTableData(data);
|
||||
}
|
||||
// TODO, try to convert JSON to table?
|
||||
console.warn('Can not convert', data);
|
||||
throw new Error('Unsupported data format');
|
||||
});
|
||||
};
|
||||
|
||||
export function sortTableData(data: TableData, sortIndex?: number, reverse = false): TableData {
|
||||
if (isNumber(sortIndex)) {
|
||||
const copy = {
|
||||
|
||||
@@ -4,18 +4,45 @@ import isNumber from 'lodash/isNumber';
|
||||
import { colors } from './colors';
|
||||
|
||||
// Types
|
||||
import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types';
|
||||
import { getFlotPairs } from './flotPairs';
|
||||
import { TimeSeriesVMs, NullValueMode, TimeSeriesValue, TableData } from '../types';
|
||||
|
||||
interface Options {
|
||||
timeSeries: TimeSeries[];
|
||||
data: TableData[];
|
||||
xColumn?: number; // Time (or null to guess)
|
||||
yColumn?: number; // Value (or null to guess)
|
||||
nullValueMode: NullValueMode;
|
||||
}
|
||||
|
||||
export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs {
|
||||
const vmSeries = timeSeries.map((item, index) => {
|
||||
// NOTE: this should move to processTableData.ts
|
||||
// I left it as is so the merge changes are more clear.
|
||||
export function processTimeSeries({ data, xColumn, yColumn, nullValueMode }: Options): TimeSeriesVMs {
|
||||
const vmSeries = data.map((item, index) => {
|
||||
if (!isNumber(xColumn)) {
|
||||
xColumn = 1; // Default timeseries colum. TODO, find first time field!
|
||||
}
|
||||
if (!isNumber(yColumn)) {
|
||||
yColumn = 0; // TODO, find first non-time field
|
||||
}
|
||||
|
||||
// TODO? either % or throw error?
|
||||
if (xColumn >= item.columns.length) {
|
||||
throw new Error('invalid colum: ' + xColumn);
|
||||
}
|
||||
if (yColumn >= item.columns.length) {
|
||||
throw new Error('invalid colum: ' + yColumn);
|
||||
}
|
||||
|
||||
const colorIndex = index % colors.length;
|
||||
const label = item.target;
|
||||
const result = [];
|
||||
const label = item.columns[yColumn].text;
|
||||
|
||||
// Use external calculator just to make sure it works :)
|
||||
const result = getFlotPairs({
|
||||
rows: item.rows,
|
||||
xIndex: xColumn,
|
||||
yIndex: yColumn,
|
||||
nullValueMode,
|
||||
});
|
||||
|
||||
// stat defaults
|
||||
let total = 0;
|
||||
@@ -42,9 +69,9 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
|
||||
let previousValue = 0;
|
||||
let previousDeltaUp = true;
|
||||
|
||||
for (let i = 0; i < item.datapoints.length; i++) {
|
||||
currentValue = item.datapoints[i][0];
|
||||
currentTime = item.datapoints[i][1];
|
||||
for (let i = 0; i < item.rows.length; i++) {
|
||||
currentValue = item.rows[i][yColumn];
|
||||
currentTime = item.rows[i][xColumn];
|
||||
|
||||
if (typeof currentTime !== 'number') {
|
||||
continue;
|
||||
@@ -95,7 +122,7 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
|
||||
if (previousValue > currentValue) {
|
||||
// counter reset
|
||||
previousDeltaUp = false;
|
||||
if (i === item.datapoints.length - 1) {
|
||||
if (i === item.rows.length - 1) {
|
||||
// reset on last
|
||||
delta += currentValue;
|
||||
}
|
||||
@@ -118,8 +145,6 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
|
||||
allIsZero = false;
|
||||
}
|
||||
}
|
||||
|
||||
result.push([currentTime, currentValue]);
|
||||
}
|
||||
|
||||
if (max === -Number.MAX_VALUE) {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
|
||||
import { processTimeSeries } from './processTimeSeries';
|
||||
|
||||
export interface SingleStatProcessingOptions {
|
||||
panelData: PanelData;
|
||||
stat: string;
|
||||
}
|
||||
|
||||
//
|
||||
// This is a temporary thing, waiting for a better data model and maybe unification between time series & table data
|
||||
//
|
||||
export function processSingleStatPanelData(options: SingleStatProcessingOptions): SingleStatValueInfo[] {
|
||||
const { panelData, stat } = options;
|
||||
|
||||
if (panelData.timeSeries) {
|
||||
const timeSeries = processTimeSeries({
|
||||
timeSeries: panelData.timeSeries,
|
||||
nullValueMode: NullValueMode.Null,
|
||||
});
|
||||
|
||||
return timeSeries.map((series, index) => {
|
||||
const value = stat !== 'name' ? series.stats[stat] : series.label;
|
||||
|
||||
return {
|
||||
value: value,
|
||||
};
|
||||
});
|
||||
} else if (panelData.tableData) {
|
||||
throw { message: 'Panel data not supported' };
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
interface StateHolderProps<T> {
|
||||
initialState: T;
|
||||
children: (currentState: T, updateState: (nextState: T) => void) => JSX.Element;
|
||||
children: (currentState: T, updateState: (nextState: T) => void) => React.ReactNode;
|
||||
}
|
||||
|
||||
export class UseState<T> extends React.Component<StateHolderProps<T>, { value: T; initialState: T }> {
|
||||
|
||||
@@ -101,7 +101,7 @@ func (dc *databaseCache) Set(key string, value interface{}, expire time.Duration
|
||||
|
||||
// insert or update depending on if item already exist
|
||||
if has {
|
||||
sql := `UPDATE cache_data SET data=?, created=?, expire=? WHERE cache_key='?'`
|
||||
sql := `UPDATE cache_data SET data=?, created_at=?, expires=? WHERE cache_key=?`
|
||||
_, err = session.Exec(sql, data, getTime().Unix(), expiresInSeconds, key)
|
||||
} else {
|
||||
sql := `INSERT INTO cache_data (cache_key,data,created_at,expires) VALUES(?,?,?,?)`
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
@@ -54,3 +53,20 @@ func TestDatabaseStorageGarbageCollection(t *testing.T) {
|
||||
_, err = db.Get("key5")
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
||||
func TestSecondSet(t *testing.T) {
|
||||
var err error
|
||||
sqlstore := sqlstore.InitTestDB(t)
|
||||
|
||||
db := &databaseCache{
|
||||
SQLStore: sqlstore,
|
||||
log: log.New("remotecache.database"),
|
||||
}
|
||||
|
||||
obj := &CacheableStruct{String: "hey!"}
|
||||
|
||||
err = db.Set("killa-gorilla", obj, 0)
|
||||
err = db.Set("killa-gorilla", obj, 0)
|
||||
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func (r RoleType) Includes(other RoleType) bool {
|
||||
return other != ROLE_ADMIN
|
||||
}
|
||||
|
||||
if r == ROLE_VIEWER {
|
||||
return other == ROLE_VIEWER
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -40,137 +40,142 @@ var regionCache sync.Map
|
||||
|
||||
func init() {
|
||||
metricsMap = map[string][]string{
|
||||
"AWS/AmazonMQ": {"CpuUtilization", "HeapUsage", "NetworkIn", "NetworkOut", "TotalMessageCount", "ConsumerCount", "EnqueueCount", "EnqueueTime", "ExpiredCount", "InflightCount", "DispatchCount", "DequeueCount", "MemoryUsage", "ProducerCount", "QueueSize"},
|
||||
"AWS/ApiGateway": {"4XXError", "5XXError", "CacheHitCount", "CacheMissCount", "Count", "IntegrationLatency", "Latency"},
|
||||
"AWS/ApplicationELB": {"ActiveConnectionCount", "ClientTLSNegotiationErrorCount", "HealthyHostCount", "HTTPCode_ELB_4XX_Count", "HTTPCode_ELB_5XX_Count", "HTTPCode_Target_2XX_Count", "HTTPCode_Target_3XX_Count", "HTTPCode_Target_4XX_Count", "HTTPCode_Target_5XX_Count", "IPv6ProcessedBytes", "IPv6RequestCount", "NewConnectionCount", "ProcessedBytes", "RejectedConnectionCount", "RequestCount", "RequestCountPerTarget", "TargetConnectionErrorCount", "TargetResponseTime", "TargetTLSNegotiationErrorCount", "UnHealthyHostCount"},
|
||||
"AWS/AutoScaling": {"GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"},
|
||||
"AWS/Billing": {"EstimatedCharges"},
|
||||
"AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
|
||||
"AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
|
||||
"AWS/CloudHSM": {"HsmUnhealthy", "HsmTemperature", "HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSslCtxsOccupied", "HsmSessionCount", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"},
|
||||
"AWS/CodeBuild": {"BuildDuration", "Builds", "DownloadSourceDuration", "Duration", "FailedBuilds", "FinalizingDuration", "InstallDuration", "PostBuildDuration", "PreBuildDuration", "ProvisioningDuration", "QueuedDuration", "SubmittedDuration", "SucceededBuilds", "UploadArtifactsDuration"},
|
||||
"AWS/Connect": {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"},
|
||||
"AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"},
|
||||
"AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"},
|
||||
"AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"},
|
||||
"AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"},
|
||||
"AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
||||
"AWS/EC2/API": {"ClientErrors", "RequestLimitExceeded", "ServerErrors", "SuccessfulCalls"},
|
||||
"AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"},
|
||||
"AWS/ECS": {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"},
|
||||
"AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"},
|
||||
"AWS/ELB": {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount", "EstimatedALBActiveConnectionCount", "EstimatedALBConsumedLCUs", "EstimatedALBNewConnectionCount", "EstimatedProcessedBytes"},
|
||||
"AWS/ElastiCache": {
|
||||
"CPUUtilization", "FreeableMemory", "NetworkBytesIn", "NetworkBytesOut", "SwapUsage",
|
||||
"BytesUsedForCacheItems", "BytesReadIntoMemcached", "BytesWrittenOutFromMemcached", "CasBadval", "CasHits", "CasMisses", "CmdFlush", "CmdGet", "CmdSet", "CurrConnections", "CurrItems", "DecrHits", "DecrMisses", "DeleteHits", "DeleteMisses", "Evictions", "GetHits", "GetMisses", "IncrHits", "IncrMisses", "Reclaimed",
|
||||
"BytesUsedForHash", "CmdConfigGet", "CmdConfigSet", "CmdTouch", "CurrConfig", "EvictedUnfetched", "ExpiredUnfetched", "SlabsMoved", "TouchHits", "TouchMisses",
|
||||
"NewConnections", "NewItems", "UnusedMemory",
|
||||
"BytesUsedForCache", "CacheHits", "CacheMisses", "CurrConnections", "Evictions", "HyperLogLogBasedCmds", "NewConnections", "Reclaimed", "ReplicationBytes", "ReplicationLag", "SaveInProgress",
|
||||
"CurrItems", "GetTypeCmds", "HashBasedCmds", "KeyBasedCmds", "ListBasedCmds", "SetBasedCmds", "SetTypeCmds", "SortedSetBasedCmds", "StringBasedCmds",
|
||||
},
|
||||
"AWS/ElasticBeanstalk": {
|
||||
"EnvironmentHealth",
|
||||
"ApplicationLatencyP10", "ApplicationLatencyP50", "ApplicationLatencyP75", "ApplicationLatencyP85", "ApplicationLatencyP90", "ApplicationLatencyP95", "ApplicationLatencyP99", "ApplicationLatencyP99.9",
|
||||
"ApplicationRequests2xx", "ApplicationRequests3xx", "ApplicationRequests4xx", "ApplicationRequests5xx", "ApplicationRequestsTotal",
|
||||
"CPUIdle", "CPUIowait", "CPUIrq", "CPUNice", "CPUSoftirq", "CPUSystem", "CPUUser",
|
||||
"InstanceHealth", "InstancesDegraded", "InstancesInfo", "InstancesNoData", "InstancesOk", "InstancesPending", "InstancesSevere", "InstancesUnknown", "InstancesWarning",
|
||||
"LoadAverage1min", "LoadAverage5min",
|
||||
"RootFilesystemUtil",
|
||||
},
|
||||
"AWS/ElasticMapReduce": {"IsIdle", "JobsRunning", "JobsFailed",
|
||||
"MapTasksRunning", "MapTasksRemaining", "MapSlotsOpen", "RemainingMapTasksPerSlot", "ReduceTasksRunning", "ReduceTasksRemaining", "ReduceSlotsOpen",
|
||||
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "TaskNodesRunning", "TaskNodesPending", "LiveTaskTrackers",
|
||||
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "TotalLoad",
|
||||
"BackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup",
|
||||
"IsIdle", "ContainerAllocated", "ContainerReserved", "ContainerPending", "AppsCompleted", "AppsFailed", "AppsKilled", "AppsPending", "AppsRunning", "AppsSubmitted",
|
||||
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "MRTotalNodes", "MRActiveNodes", "MRLostNodes", "MRUnhealthyNodes", "MRDecommissionedNodes", "MRRebootedNodes",
|
||||
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "CorruptBlocks", "TotalLoad", "MemoryTotalMB", "MemoryReservedMB", "MemoryAvailableMB", "MemoryAllocatedMB", "PendingDeletionBlocks", "UnderReplicatedBlocks", "DfsPendingReplicationBlocks", "CapacityRemainingGB",
|
||||
"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
|
||||
"AWS/ES": {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "ClusterUsedSpace", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUCreditBalance", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUCreditBalance", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueDepth", "ReadIOPS", "WriteIOPS"},
|
||||
"AWS/Events": {"Invocations", "FailedInvocations", "TriggeredRules", "MatchedEvents", "ThrottledRules"},
|
||||
"AWS/Firehose": {"DeliveryToElasticsearch.Bytes", "DeliveryToElasticsearch.Records", "DeliveryToElasticsearch.Success", "DeliveryToRedshift.Bytes", "DeliveryToRedshift.Records", "DeliveryToRedshift.Success", "DeliveryToS3.Bytes", "DeliveryToS3.DataFreshness", "DeliveryToS3.Records", "DeliveryToS3.Success", "IncomingBytes", "IncomingRecords", "DescribeDeliveryStream.Latency", "DescribeDeliveryStream.Requests", "ListDeliveryStreams.Latency", "ListDeliveryStreams.Requests", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Requests", "PutRecordBatch.Bytes", "PutRecordBatch.Latency", "PutRecordBatch.Records", "PutRecordBatch.Requests", "UpdateDeliveryStream.Latency", "UpdateDeliveryStream.Requests"},
|
||||
"AWS/IoT": {"PublishIn.Success", "PublishOut.Success", "Subscribe.Success", "Ping.Success", "Connect.Success", "GetThingShadow.Accepted"},
|
||||
"AWS/Kinesis": {"GetRecords.Bytes", "GetRecords.IteratorAge", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Records", "GetRecords.Success", "IncomingBytes", "IncomingRecords", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "ReadProvisionedThroughputExceeded", "WriteProvisionedThroughputExceeded", "IteratorAgeMilliseconds", "OutgoingBytes", "OutgoingRecords"},
|
||||
"AWS/KinesisAnalytics": {"Bytes", "MillisBehindLatest", "Records", "Success"},
|
||||
"AWS/Lambda": {"Invocations", "Errors", "Duration", "Throttles", "IteratorAge"},
|
||||
"AWS/AppSync": {"Latency", "4XXError", "5XXError"},
|
||||
"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/Neptune": {"CPUUtilization", "ClusterReplicaLag", "ClusterReplicaLagMaximum", "ClusterReplicaLagMinimum", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "GremlinHttp1xx", "GremlinHttp2xx", "GremlinHttp4xx", "GremlinHttp5xx", "GremlinErrors", "GremlinRequests", "GremlinRequestsPerSec", "GremlinWebSocketSuccess", "GremlinWebSocketClientErrors", "GremlinWebSocketServerErrors", "GremlinWebSocketAvailableConnections", "Http1xx", "Http2xx", "Http4xx", "Http5xx", "Http100", "Http101", "Http200", "Http400", "Http403", "Http405", "Http413", "Http429", "Http500", "Http501", "LoaderErrors", "LoaderRequests", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "SparqlHttp1xx", "SparqlHttp2xx", "SparqlHttp4xx", "SparqlHttp5xx", "SparqlErrors", "SparqlRequests", "SparqlRequestsPerSec", "StatusErrors", "StatusRequests", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs"},
|
||||
"AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"},
|
||||
"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", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "MaximumUsedTransactionIDs", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "ServerlessDatabaseCapacity", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"},
|
||||
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"},
|
||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send", "Reputation.BounceRate", "Reputation.ComplaintRate"},
|
||||
"AWS/SNS": {"NumberOfMessagesPublished", "PublishSize", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed"},
|
||||
"AWS/SQS": {"NumberOfMessagesSent", "SentMessageSize", "NumberOfMessagesReceived", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "ApproximateAgeOfOldestMessage", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesVisible", "ApproximateNumberOfMessagesNotVisible"},
|
||||
"AWS/States": {"ExecutionTime", "ExecutionThrottled", "ExecutionsAborted", "ExecutionsFailed", "ExecutionsStarted", "ExecutionsSucceeded", "ExecutionsTimedOut", "ActivityRunTime", "ActivityScheduleTime", "ActivityTime", "ActivitiesFailed", "ActivitiesHeartbeatTimedOut", "ActivitiesScheduled", "ActivitiesScheduled", "ActivitiesSucceeded", "ActivitiesTimedOut", "LambdaFunctionRunTime", "LambdaFunctionScheduleTime", "LambdaFunctionTime", "LambdaFunctionsFailed", "LambdaFunctionsHeartbeatTimedOut", "LambdaFunctionsScheduled", "LambdaFunctionsStarted", "LambdaFunctionsSucceeded", "LambdaFunctionsTimedOut"},
|
||||
"AWS/StorageGateway": {"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "CloudBytesDownloaded", "CloudDownloadLatency", "CloudBytesUploaded", "UploadBufferFree", "UploadBufferPercentUsed", "UploadBufferUsed", "QueuedWrites", "ReadBytes", "ReadTime", "TotalCacheSize", "WriteBytes", "WriteTime", "TimeSinceLastRecoveryPoint", "WorkingStorageFree", "WorkingStoragePercentUsed", "WorkingStorageUsed",
|
||||
"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"},
|
||||
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut",
|
||||
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
|
||||
"AWS/VPN": {"TunnelState", "TunnelDataIn", "TunnelDataOut"},
|
||||
"Rekognition": {"SuccessfulRequestCount", "ThrottledCount", "ResponseTime", "DetectedFaceCount", "DetectedLabelCount", "ServerErrorCount", "UserErrorCount"},
|
||||
"WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
|
||||
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
|
||||
"KMS": {"SecondsUntilKeyMaterialExpiration"},
|
||||
"AWS/AmazonMQ": {"ConsumerCount", "CpuCreditBalance", "CpuUtilization", "CurrentConnectionsCount", "DequeueCount", "DispatchCount", "EnqueueCount", "EnqueueTime", "ExpiredCount", "HeapUsage", "InflightCount", "JournalFilesForFastRecovery", "JournalFilesForFullRecovery", "MemoryUsage", "NetworkIn", "NetworkOut", "OpenTransactionsCount", "ProducerCount", "QueueSize", "StorePercentUsage", "TotalConsumerCount", "TotalMessageCount", "TotalProducerCount"},
|
||||
"AWS/ApiGateway": {"4XXError", "5XXError", "CacheHitCount", "CacheMissCount", "Count", "IntegrationLatency", "Latency"},
|
||||
"AWS/AppStream": {"ActualCapacity", "AvailableCapacity", "CapacityUtilization", "DesiredCapacity", "InUseCapacity", "InsufficientCapacityError", "PendingCapacity", "RunningCapacity"},
|
||||
"AWS/AppSync": {"4XXError", "5XXError", "Latency"},
|
||||
"AWS/ApplicationELB": {"ActiveConnectionCount", "ClientTLSNegotiationErrorCount", "ConsumedLCUs", "ELBAuthError", "ELBAuthFailure", "ELBAuthLatency", "ELBAuthRefreshTokenSuccess", "ELBAuthSuccess", "ELBAuthUserClaimsSizeExceeded", "HTTPCode_ELB_3XX_Count", "HTTPCode_ELB_4XX_Count", "HTTPCode_ELB_5XX_Count", "HTTPCode_Target_2XX_Count", "HTTPCode_Target_3XX_Count", "HTTPCode_Target_4XX_Count", "HTTPCode_Target_5XX_Count", "HTTP_Fixed_Response_Count", "HTTP_Redirect_Count", "HTTP_Redirect_Url_Limit_Exceeded_Count", "HealthyHostCount", "IPv6ProcessedBytes", "IPv6RequestCount", "LambdaInternalError", "LambdaTargetProcessedBytes", "LambdaUserError", "NewConnectionCount", "NonStickyRequestCount", "ProcessedBytes", "RejectedConnectionCount", "RequestCount", "RequestCountPerTarget", "RuleEvaluations", "StandardProcessedBytes", "TargetConnectionErrorCount", "TargetResponseTime", "TargetTLSNegotiationErrorCount", "UnHealthyHostCount"},
|
||||
"AWS/AutoScaling": {"GroupDesiredCapacity", "GroupInServiceInstances", "GroupMaxSize", "GroupMinSize", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"},
|
||||
"AWS/Billing": {"EstimatedCharges"},
|
||||
"AWS/CloudFront": {"4xxErrorRate", "5xxErrorRate", "BytesDownloaded", "BytesUploaded", "Requests", "TotalErrorRate"},
|
||||
"AWS/CloudHSM": {"HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSessionCount", "HsmSslCtxsOccupied", "HsmTemperature", "HsmUnhealthy", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"},
|
||||
"AWS/CloudSearch": {"IndexUtilization", "Partitions", "SearchableDocuments", "SuccessfulRequests"},
|
||||
"AWS/CodeBuild": {"BuildDuration", "Builds", "DownloadSourceDuration", "Duration", "FailedBuilds", "FinalizingDuration", "InstallDuration", "PostBuildDuration", "PreBuildDuration", "ProvisioningDuration", "QueuedDuration", "SubmittedDuration", "SucceededBuilds", "UploadArtifactsDuration"},
|
||||
"AWS/Connect": {"CallBackNotDialableNumber", "CallRecordingUploadError", "CallsBreachingConcurrencyQuota", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MisconfiguredPhoneNumbers", "MissedCalls", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"},
|
||||
"AWS/DDoSProtection": {"AllowedRequests", "BlockedRequests", "CountedRequests", "DDoSAttackBitsPerSecond", "DDoSAttackPacketsPerSecond", "DDoSAttackRequestsPerSecond", "DDoSDetected", "PassedRequests"},
|
||||
"AWS/DMS": {"CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCIncomingChanges", "CDCLatencySource", "CDCLatencyTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "FreeableMemory", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SwapUsage", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/DX": {"ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelRx", "ConnectionLightLevelTx", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionState"},
|
||||
"AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "PendingReplicationCount", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReplicationLatency", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "ThrottledRequests", "TimeToLiveDeletedItemCount", "UserErrors", "WriteThrottleEvents"},
|
||||
"AWS/EBS": {"BurstBalance", "VolumeConsumedReadWriteOps", "VolumeIdleTime", "VolumeQueueLength", "VolumeReadBytes", "VolumeReadOps", "VolumeThroughputPercentage", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeWriteBytes", "VolumeWriteOps"},
|
||||
"AWS/EC2": {"CPUCreditBalance", "CPUCreditUsage", "CPUSurplusCreditBalance", "CPUSurplusCreditsCharged", "CPUUtilization", "DiskReadBytes", "DiskReadOps", "DiskWriteBytes", "DiskWriteOps", "EBSByteBalance%", "EBSIOBalance%", "EBSReadBytes", "EBSReadOps", "EBSWriteBytes", "EBSWriteOps", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
||||
"AWS/EC2/API": {"ClientErrors", "RequestLimitExceeded", "ServerErrors", "SuccessfulCalls"},
|
||||
"AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"},
|
||||
"AWS/ECS": {"CPUReservation", "CPUUtilization", "GPUReservation", "MemoryReservation", "MemoryUtilization"},
|
||||
"AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "PercentIOLimit", "PermittedThroughput", "TotalIOBytes"},
|
||||
"AWS/ELB": {"BackendConnectionErrors", "EstimatedALBActiveConnectionCount", "EstimatedALBConsumedLCUs", "EstimatedALBNewConnectionCount", "EstimatedProcessedBytes", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HealthyHostCount", "Latency", "RequestCount", "SpilloverCount", "SurgeQueueLength", "UnHealthyHostCount"},
|
||||
"AWS/ES": {"AutomatedSnapshotFailure", "CPUCreditBalance", "CPUUtilization", "ClusterIndexWritesBlocked", "ClusterStatus.green", "ClusterStatus.red", "ClusterStatus.yellow", "ClusterUsedSpace", "DeletedDocuments", "DiskQueueDepth", "ElasticsearchRequests", "FreeStorageSpace", "IndexingLatency", "IndexingRate", "InvalidHostHeaderRequests", "JVMGCOldCollectionCount", "JVMGCOldCollectionTime", "JVMGCYoungCollectionCount", "JVMGCYoungCollectionTime", "JVMMemoryPressure", "KMSKeyError", "KMSKeyInaccessible", "KibanaHealthyNodes", "MasterCPUCreditBalance", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "MasterReachableFromNode", "Nodes", "ReadIOPS", "ReadLatency", "ReadThroughput", "RequestCount", "SearchLatency", "SearchRate", "SearchableDocuments", "SysMemoryUtilization", "ThreadpoolBulkQueue", "ThreadpoolBulkRejected", "ThreadpoolBulkThreads", "ThreadpoolForce_mergeQueue", "ThreadpoolForce_mergeRejected", "ThreadpoolForce_mergeThreads", "ThreadpoolIndexQueue", "ThreadpoolIndexRejected", "ThreadpoolIndexThreads", "ThreadpoolSearchQueue", "ThreadpoolSearchRejected", "ThreadpoolSearchThreads", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/ElastiCache": {"ActiveDefragHits", "BytesReadIntoMemcached", "BytesUsedForCache", "BytesUsedForCacheItems", "BytesUsedForHash", "BytesWrittenOutFromMemcached", "CPUUtilization", "CacheHits", "CacheMisses", "CasBadval", "CasHits", "CasMisses", "CmdConfigGet", "CmdConfigSet", "CmdFlush", "CmdGet", "CmdSet", "CmdTouch", "CurrConfig", "CurrConnections", "CurrItems", "DecrHits", "DecrMisses", "DeleteHits", "DeleteMisses", "EngineCPUUtilization", "EvictedUnfetched", "Evictions", "ExpiredUnfetched", "FreeableMemory", "GetHits", "GetMisses", "GetTypeCmds", "HashBasedCmds", "HyperLogLogBasedCmds", "IncrHits", "IncrMisses", "KeyBasedCmds", "ListBasedCmds", "NetworkBytesIn", "NetworkBytesOut", "NewConnections", "NewItems", "Reclaimed", "ReplicationBytes", "ReplicationLag", "SaveInProgress", "SetBasedCmds", "SetTypeCmds", "SlabsMoved", "SortedSetBasedCmds", "StringBasedCmds", "SwapUsage", "TouchHits", "TouchMisses", "UnusedMemory"},
|
||||
"AWS/ElasticBeanstalk": {"ApplicationLatencyP10", "ApplicationLatencyP50", "ApplicationLatencyP75", "ApplicationLatencyP85", "ApplicationLatencyP90", "ApplicationLatencyP95", "ApplicationLatencyP99", "ApplicationLatencyP99.9", "ApplicationRequests2xx", "ApplicationRequests3xx", "ApplicationRequests4xx", "ApplicationRequests5xx", "ApplicationRequestsTotal", "CPUIdle", "CPUIowait", "CPUIrq", "CPUNice", "CPUSoftirq", "CPUSystem", "CPUUser", "EnvironmentHealth", "InstanceHealth", "InstancesDegraded", "InstancesInfo", "InstancesNoData", "InstancesOk", "InstancesPending", "InstancesSevere", "InstancesUnknown", "InstancesWarning", "LoadAverage1min", "LoadAverage5min", "RootFilesystemUtil"},
|
||||
"AWS/ElasticMapReduce": {"AppsCompleted", "AppsFailed", "AppsKilled", "AppsPending", "AppsRunning", "AppsSubmitted", "BackupFailed", "CapacityRemainingGB", "Cluster Status", "ContainerAllocated", "ContainerPending", "ContainerPendingRatio", "ContainerReserved", "CoreNodesPending", "CoreNodesRunning", "CorruptBlocks", "DfsPendingReplicationBlocks", "HBase", "HDFSBytesRead", "HDFSBytesWritten", "HDFSUtilization", "HbaseBackupFailed", "IO", "IsIdle", "JobsFailed", "JobsRunning", "LiveDataNodes", "LiveTaskTrackers", "MRActiveNodes", "MRDecommissionedNodes", "MRLostNodes", "MRRebootedNodes", "MRTotalNodes", "MRUnhealthyNodes", "Map/Reduce", "MapSlotsOpen", "MapTasksRemaining", "MapTasksRunning", "MemoryAllocatedMB", "MemoryAvailableMB", "MemoryReservedMB", "MemoryTotalMB", "MissingBlocks", "MostRecentBackupDuration", "Node Status", "PendingDeletionBlocks", "ReduceSlotsOpen", "ReduceTasksRemaining", "ReduceTasksRunning", "RemainingMapTasksPerSlot", "S3BytesRead", "S3BytesWritten", "TaskNodesPending", "TaskNodesRunning", "TimeSinceLastSuccessfulBackup", "TotalLoad", "UnderReplicatedBlocks", "YARNMemoryAvailablePercentage"},
|
||||
"AWS/ElasticTranscoder": {"Billed Audio Output", "Billed HD Output", "Billed SD Output", "Errors", "Jobs Completed", "Jobs Errored", "Outputs per Job", "Standby Time", "Throttles"},
|
||||
"AWS/Events": {"DeadLetterInvocations", "FailedInvocations", "Invocations", "MatchedEvents", "ThrottledRules", "TriggeredRules"},
|
||||
"AWS/FSx": {"DataReadBytes", "DataReadOperations", "DataWriteBytes", "DataWriteOperations", "FreeDataStorageCapacity", "MetadataOperations"},
|
||||
"AWS/Firehose": {"BackupToS3.Bytes", "BackupToS3.DataFreshness", "BackupToS3.Records", "BackupToS3.Success", "DataReadFromKinesisStream.Bytes", "DataReadFromKinesisStream.Records", "DeliveryToElasticsearch.Bytes", "DeliveryToElasticsearch.Records", "DeliveryToElasticsearch.Success", "DeliveryToRedshift.Bytes", "DeliveryToRedshift.Records", "DeliveryToRedshift.Success", "DeliveryToS3.Bytes", "DeliveryToS3.DataFreshness", "DeliveryToS3.Records", "DeliveryToS3.Success", "DeliveryToSplunk.Bytes", "DeliveryToSplunk.DataFreshness", "DeliveryToSplunk.Records", "DeliveryToSplunk.Success", "DescribeDeliveryStream.Latency", "DescribeDeliveryStream.Requests", "ExecuteProcessing.Duration", "ExecuteProcessing.Success", "FailedConversion.Bytes", "FailedConversion.Records", "IncomingBytes", "IncomingRecords", "KinesisMillisBehindLatest", "ListDeliveryStreams.Latency", "ListDeliveryStreams.Requests", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Requests", "PutRecordBatch.Bytes", "PutRecordBatch.Latency", "PutRecordBatch.Records", "PutRecordBatch.Requests", "SucceedConversion.Bytes", "SucceedConversion.Records", "SucceedProcessing.Bytes", "SucceedProcessing.Records", "ThrottledDescribeStream", "ThrottledGetRecords", "ThrottledGetShardIterator", "UpdateDeliveryStream.Latency", "UpdateDeliveryStream.Requests"},
|
||||
"AWS/Glue": {"glue.driver.BlockManager.disk.diskSpaceUsed_MB", "glue.driver.ExecutorAllocationManager.executors.numberAllExecutors", "glue.driver.ExecutorAllocationManager.executors.numberMaxNeededExecutors", "glue.driver.aggregate.bytesRead", "glue.driver.aggregate.elapsedTime", "glue.driver.aggregate.numCompletedStages", "glue.driver.aggregate.numCompletedTasks", "glue.driver.aggregate.numFailedTasks", "glue.driver.aggregate.numKilledTasks", "glue.driver.aggregate.recordsRead", "glue.driver.aggregate.shuffleBytesWritten", "glue.driver.aggregate.shuffleLocalBytesRead", "glue.driver.jvm.heap.usage glue.executorId.jvm.heap.usage glue.ALL.jvm.heap.usage", "glue.driver.jvm.heap.used glue.executorId.jvm.heap.used glue.ALL.jvm.heap.used", "glue.driver.s3.filesystem.read_bytes glue.executorId.s3.filesystem.read_bytes glue.ALL.s3.filesystem.read_bytes", "glue.driver.s3.filesystem.write_bytes glue.executorId.s3.filesystem.write_bytes glue.ALL.s3.filesystem.write_bytes", "glue.driver.system.cpuSystemLoad glue.executorId.system.cpuSystemLoad glue.ALL.system.cpuSystemLoad"},
|
||||
"AWS/Inspector": {"TotalAssessmentRunFindings", "TotalAssessmentRuns", "TotalHealthyAgents", "TotalMatchingAgents"},
|
||||
"AWS/IoT": {"Connect.Success", "GetThingShadow.Accepted", "Ping.Success", "PublishIn.Success", "PublishOut.Success", "Subscribe.Success"},
|
||||
"AWS/KMS": {"SecondsUntilKeyMaterialExpiration"},
|
||||
"AWS/Kinesis": {"GetRecords.Bytes", "GetRecords.IteratorAge", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Records", "GetRecords.Success", "IncomingBytes", "IncomingRecords", "IteratorAgeMilliseconds", "OutgoingBytes", "OutgoingRecords", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "ReadProvisionedThroughputExceeded", "SubscribeToShard.RateExceeded", "SubscribeToShard.Success", "SubscribeToShardEvent.Bytes", "SubscribeToShardEvent.MillisBehindLatest", "SubscribeToShardEvent.Records", "SubscribeToShardEvent.Success", "WriteProvisionedThroughputExceeded"},
|
||||
"AWS/KinesisAnalytics": {"Bytes", "InputProcessing.DroppedRecords", "InputProcessing.Duration", "InputProcessing.OkBytes", "InputProcessing.OkRecords", "InputProcessing.ProcessingFailedRecords", "InputProcessing.Success", "KPUs", "LambdaDelivery.DeliveryFailedRecords", "LambdaDelivery.Duration", "LambdaDelivery.OkRecords", "MillisBehindLatest", "Records", "Success"},
|
||||
"AWS/KinesisVideo": {"GetHLSMasterPlaylist.Latency", "GetHLSMasterPlaylist.Requests", "GetHLSMasterPlaylist.Success", "GetHLSMediaPlaylist.Latency", "GetHLSMediaPlaylist.Requests", "GetHLSMediaPlaylist.Success", "GetHLSStreamingSessionURL.Latency", "GetHLSStreamingSessionURL.Requests", "GetHLSStreamingSessionURL.Success", "GetMP4InitFragment.Latency", "GetMP4InitFragment.Requests", "GetMP4InitFragment.Success", "GetMP4MediaFragment.Latency", "GetMP4MediaFragment.OutgoingBytes", "GetMP4MediaFragment.Requests", "GetMP4MediaFragment.Success", "GetMedia.ConnectionErrors", "GetMedia.MillisBehindNow", "GetMedia.OutgoingBytes", "GetMedia.OutgoingFragments", "GetMedia.OutgoingFrames", "GetMedia.Requests", "GetMedia.Success", "GetMediaForFragmentList.OutgoingBytes", "GetMediaForFragmentList.OutgoingFragments", "GetMediaForFragmentList.OutgoingFrames", "GetMediaForFragmentList.Requests", "GetMediaForFragmentList.Success", "GetTSFragment.Latency", "GetTSFragment.OutgoingBytes", "GetTSFragment.Requests", "GetTSFragment.Success", "ListFragments.Latency", "PutMedia.ActiveConnections", "PutMedia.BufferingAckLatency", "PutMedia.ConnectionErrors", "PutMedia.ErrorAckCount", "PutMedia.FragmentIngestionLatency", "PutMedia.FragmentPersistLatency", "PutMedia.IncomingBytes", "PutMedia.IncomingFragments", "PutMedia.IncomingFrames", "PutMedia.Latency", "PutMedia.PersistedAckLatency", "PutMedia.ReceivedAckLatency", "PutMedia.Requests", "PutMedia.Success"},
|
||||
"AWS/Lambda": {"ConcurrentExecutions", "DeadLetterErrors", "Duration", "Errors", "Invocations", "IteratorAge", "Throttles", "UnreservedConcurrentExecutions"},
|
||||
"AWS/Lex": {"BotChannelAuthErrors", "BotChannelConfigurationErrors", "BotChannelInboundThrottledEvents", "BotChannelOutboundThrottledEvents", "BotChannelRequestCount", "BotChannelResponseCardErrors", "BotChannelSystemErrors", "MissedUtteranceCount", "RuntimeInvalidLambdaResponses", "RuntimeLambdaErrors", "RuntimePollyErrors", "RuntimeRequestCount", "RuntimeSucessfulRequestLatency", "RuntimeSystemErrors", "RuntimeThrottledEvents", "RuntimeUserErrors"},
|
||||
"AWS/Logs": {"DeliveryErrors", "DeliveryThrottling", "ForwardedBytes", "ForwardedLogEvents", "IncomingBytes", "IncomingLogEvents"},
|
||||
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
||||
"AWS/MediaConvert": {"AudioOutputSeconds", "Errors", "HDOutputSeconds", "JobsCompletedCount", "JobsErroredCount", "SDOutputSeconds", "StandbyTime", "TranscodingTime", "UHDOutputSeconds"},
|
||||
"AWS/MediaPackage": {"ActiveInput", "EgressBytes", "EgressRequestCount", "EgressResponseTime", "IngressBytes", "IngressResponseTime"},
|
||||
"AWS/MediaTailor": {"AdDecisionServer.Ads", "AdDecisionServer.Duration", "AdDecisionServer.Errors", "AdDecisionServer.FillRate", "AdDecisionServer.Timeouts", "AdNotReady", "Avails.Duration", "Avails.FillRate", "Avails.FilledDuration", "GetManifest.Errors", "Origin.Errors", "Origin.Timeouts"},
|
||||
"AWS/NATGateway": {"ActiveConnectionCount", "BytesInFromDestination", "BytesInFromSource", "BytesOutToDestination", "BytesOutToSource", "ConnectionAttemptCount", "ConnectionEstablishedCount", "ErrorPortAllocation", "IdleTimeoutCount", "PacketsDropCount", "PacketsInFromDestination", "PacketsInFromSource", "PacketsOutToDestination", "PacketsOutToSource"},
|
||||
"AWS/Neptune": {"CPUUtilization", "ClusterReplicaLag", "ClusterReplicaLagMaximum", "ClusterReplicaLagMinimum", "EngineUptime", "FreeLocalStorage", "FreeableMemory", "GremlinErrors", "GremlinHttp1xx", "GremlinHttp2xx", "GremlinHttp4xx", "GremlinHttp5xx", "GremlinRequests", "GremlinRequestsPerSec", "GremlinWebSocketAvailableConnections", "GremlinWebSocketClientErrors", "GremlinWebSocketServerErrors", "GremlinWebSocketSuccess", "Http100", "Http101", "Http1xx", "Http200", "Http2xx", "Http400", "Http403", "Http405", "Http413", "Http429", "Http4xx", "Http500", "Http501", "Http5xx", "LoaderErrors", "LoaderRequests", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "SparqlErrors", "SparqlHttp1xx", "SparqlHttp2xx", "SparqlHttp4xx", "SparqlHttp5xx", "SparqlRequests", "SparqlRequestsPerSec", "StatusErrors", "StatusRequests", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs"},
|
||||
"AWS/NetworkELB": {"ActiveFlowCount", "ActiveFlowCount_TLS", "ClientTLSNegotiationErrorCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "NewFlowCount_TLS", "ProcessedBytes", "ProcessedBytes_TLS", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "TargetTLSNegotiationErrorCount", "UnHealthyHostCount"},
|
||||
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_steal", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_15", "load_5", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
|
||||
"AWS/Polly": {"2XXCount", "4XXCount", "5XXCount", "RequestCharacters", "ResponseLatency"},
|
||||
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "CommitLatency", "CommitThroughput", "DDLLatency", "DDLThroughput", "DMLLatency", "DMLThroughput", "DatabaseConnections", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "EngineUptime", "FailedSQLServerAgentJobsCount", "FailedSqlStatements", "FreeLocalStorage", "FreeStorageSpace", "FreeableMemory", "InsertLatency", "InsertThroughput", "LoginFailures", "MaximumUsedTransactionIDs", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "OldestReplicationSlotLag", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ReplicationSlotDiskUsage", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "ServerlessDatabaseCapacity", "SwapUsage", "TotalConnections", "TransactionLogsDiskUsage", "TransactionLogsGeneration", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "TotalTableCount", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/Route53": {"ChildHealthCheckHealthyCount", "ConnectionTime", "HealthCheckPercentageHealthy", "HealthCheckStatus", "SSLHandshakeTime", "TimeToFirstByte"},
|
||||
"AWS/S3": {"4xxErrors", "5xxErrors", "AllRequests", "BucketSizeBytes", "BytesDownloaded", "BytesUploaded", "DeleteRequests", "FirstByteLatency", "GetRequests", "HeadRequests", "ListRequests", "NumberOfObjects", "PostRequests", "PutRequests", "SelectRequests", "SelectReturnedBytes", "SelectScannedBytes", "TotalRequestLatency"},
|
||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Reputation.BounceRate", "Reputation.ComplaintRate", "Send"},
|
||||
"AWS/SNS": {"NumberOfMessagesPublished", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed", "PublishSize"},
|
||||
"AWS/SQS": {"ApproximateAgeOfOldestMessage", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesNotVisible", "ApproximateNumberOfMessagesVisible", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "NumberOfMessagesReceived", "NumberOfMessagesSent", "SentMessageSize"},
|
||||
"AWS/SWF": {"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut"},
|
||||
"AWS/SageMaker": {"CPUUtilization", "DatasetObjectsAutoAnnotated", "DatasetObjectsHumanAnnotated", "DatasetObjectsLabelingFailed", "DiskUtilization", "GPUMemoryUtilization", "GPUUtilization", "Invocation4XXErrors", "Invocation5XXErrors", "Invocations", "InvocationsPerInstance", "JobsFailed", "JobsStopped", "JobsSucceeded", "MemoryUtilization", "ModelLatency", "OverheadLatency", "TotalDatasetObjectsLabeled"},
|
||||
"AWS/States": {"ActivitiesFailed", "ActivitiesHeartbeatTimedOut", "ActivitiesScheduled", "ActivitiesStarted", "ActivitiesSucceeded", "ActivitiesTimedOut", "ActivityRunTime", "ActivityScheduleTime", "ActivityTime", "ConsumedCapacity", "ExecutionThrottled", "ExecutionTime", "ExecutionsAborted", "ExecutionsFailed", "ExecutionsStarted", "ExecutionsSucceeded", "ExecutionsTimedOut", "LambdaFunctionRunTime", "LambdaFunctionScheduleTime", "LambdaFunctionTime", "LambdaFunctionsFailed", "LambdaFunctionsHeartbeatTimedOut", "LambdaFunctionsScheduled", "LambdaFunctionsStarted", "LambdaFunctionsSucceeded", "LambdaFunctionsTimedOut", "ProvisionedBucketSize", "ProvisionedRefillRate", "ThrottledEvents"},
|
||||
"AWS/StorageGateway": {"CacheFree", "CacheHitPercent", "CachePercentDirty", "CachePercentUsed", "CacheUsed", "CloudBytesDownloaded", "CloudBytesUploaded", "CloudDownloadLatency", "QueuedWrites", "ReadBytes", "ReadTime", "TimeSinceLastRecoveryPoint", "TotalCacheSize", "UploadBufferFree", "UploadBufferPercentUsed", "UploadBufferUsed", "WorkingStorageFree", "WorkingStoragePercentUsed", "WorkingStorageUsed", "WriteBytes", "WriteTime"},
|
||||
"AWS/TransitGateway": {"BytesIn", "BytesOut", "PacketDropCountBlackhole", "PacketDropCountNoRoute", "PacketsIn", "PacketsOut"},
|
||||
"AWS/Translate": {"CharacterCount", "ResponseTime", "ServerErrorCount", "SuccessfulRequestCount", "ThrottledCount", "UserErrorCount"},
|
||||
"AWS/VPN": {"TunnelDataIn", "TunnelDataOut", "TunnelState"},
|
||||
"AWS/WorkSpaces": {"Available", "ConnectionAttempt", "ConnectionFailure", "ConnectionSuccess", "InSessionLatency", "Maintenance", "SessionDisconnect", "SessionLaunchTime", "Stopped", "Unhealthy", "UserConnected"},
|
||||
"Rekognition": {"DetectedFaceCount", "DetectedLabelCount", "ResponseTime", "ServerErrorCount", "SuccessfulRequestCount", "ThrottledCount", "UserErrorCount"},
|
||||
"WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests", "DDoSAttackBitsPerSecond", "DDoSAttackPacketsPerSecond", "DDoSAttackRequestsPerSecond", "DDoSDetected", "PassedRequests"},
|
||||
}
|
||||
dimensionsMap = map[string][]string{
|
||||
"AWS/AmazonMQ": {"Broker", "Topic", "Queue"},
|
||||
"AWS/ApiGateway": {"ApiName", "Method", "Resource", "Stage"},
|
||||
"AWS/ApplicationELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
|
||||
"AWS/AutoScaling": {"AutoScalingGroupName"},
|
||||
"AWS/Billing": {"ServiceName", "LinkedAccount", "Currency"},
|
||||
"AWS/CloudFront": {"DistributionId", "Region"},
|
||||
"AWS/CloudSearch": {},
|
||||
"AWS/CloudHSM": {"Region", "ClusterId", "HsmId"},
|
||||
"AWS/CodeBuild": {"ProjectName"},
|
||||
"AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"},
|
||||
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
|
||||
"AWS/DX": {"ConnectionId"},
|
||||
"AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation", "StreamLabel"},
|
||||
"AWS/EBS": {"VolumeId"},
|
||||
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
||||
"AWS/EC2/API": {},
|
||||
"AWS/EC2Spot": {"AvailabilityZone", "FleetRequestId", "InstanceType"},
|
||||
"AWS/ECS": {"ClusterName", "ServiceName"},
|
||||
"AWS/EFS": {"FileSystemId"},
|
||||
"AWS/ELB": {"LoadBalancerName", "AvailabilityZone"},
|
||||
"AWS/ElastiCache": {"CacheClusterId", "CacheNodeId"},
|
||||
"AWS/ElasticBeanstalk": {"EnvironmentName", "InstanceId"},
|
||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||
"AWS/ES": {"ClientId", "DomainName"},
|
||||
"AWS/Events": {"RuleName"},
|
||||
"AWS/Firehose": {"DeliveryStreamName"},
|
||||
"AWS/IoT": {"Protocol"},
|
||||
"AWS/Kinesis": {"StreamName", "ShardId"},
|
||||
"AWS/KinesisAnalytics": {"Flow", "Id", "Application"},
|
||||
"AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"},
|
||||
"AWS/AppSync": {"GraphQLAPIId"},
|
||||
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||
"AWS/NATGateway": {"NatGatewayId"},
|
||||
"AWS/Neptune": {"DBClusterIdentifier", "Role", "DatabaseClass", "EngineName"},
|
||||
"AWS/NetworkELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
|
||||
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
||||
"AWS/Redshift": {"NodeID", "ClusterIdentifier", "latency", "service class", "wmlid"},
|
||||
"AWS/RDS": {"DBInstanceIdentifier", "DBClusterIdentifier", "DbClusterIdentifier", "DatabaseClass", "EngineName", "Role"},
|
||||
"AWS/Route53": {"HealthCheckId", "Region"},
|
||||
"AWS/S3": {"BucketName", "StorageType", "FilterId"},
|
||||
"AWS/SES": {},
|
||||
"AWS/SNS": {"Application", "Platform", "TopicName"},
|
||||
"AWS/SQS": {"QueueName"},
|
||||
"AWS/States": {"StateMachineArn", "ActivityArn", "LambdaFunctionArn"},
|
||||
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
|
||||
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
|
||||
"AWS/VPN": {"VpnId", "TunnelIpAddress"},
|
||||
"Rekognition": {},
|
||||
"WAF": {"Rule", "WebACL"},
|
||||
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
|
||||
"KMS": {"KeyId"},
|
||||
"AWS/AmazonMQ": {"Broker", "Queue", "Topic"},
|
||||
"AWS/ApiGateway": {"ApiName", "Method", "Resource", "Stage"},
|
||||
"AWS/AppStream": {"Fleet"},
|
||||
"AWS/AppSync": {"GraphQLAPIId"},
|
||||
"AWS/ApplicationELB": {"AvailabilityZone", "LoadBalancer", "TargetGroup"},
|
||||
"AWS/AutoScaling": {"AutoScalingGroupName"},
|
||||
"AWS/Billing": {"Currency", "LinkedAccount", "ServiceName"},
|
||||
"AWS/CloudFront": {"DistributionId", "Region"},
|
||||
"AWS/CloudHSM": {"ClusterId", "HsmId", "Region"},
|
||||
"AWS/CloudSearch": {"ClientId", "DomainName"},
|
||||
"AWS/CodeBuild": {"ProjectName"},
|
||||
"AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"},
|
||||
"AWS/DDoSProtection": {"Region", "Rule", "RuleGroup", "WebACL"},
|
||||
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
|
||||
"AWS/DX": {"ConnectionId"},
|
||||
"AWS/DynamoDB": {"GlobalSecondaryIndexName", "Operation", "ReceivingRegion", "StreamLabel", "TableName"},
|
||||
"AWS/EBS": {"VolumeId"},
|
||||
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
||||
"AWS/EC2/API": {},
|
||||
"AWS/EC2Spot": {"AvailabilityZone", "FleetRequestId", "InstanceType"},
|
||||
"AWS/ECS": {"ClusterName", "ServiceName"},
|
||||
"AWS/EFS": {"FileSystemId"},
|
||||
"AWS/ELB": {"AvailabilityZone", "LoadBalancerName"},
|
||||
"AWS/ES": {"ClientId", "DomainName"},
|
||||
"AWS/ElastiCache": {"CacheClusterId", "CacheNodeId"},
|
||||
"AWS/ElasticBeanstalk": {"EnvironmentName", "InstanceId"},
|
||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||
"AWS/ElasticTranscoder": {"Operation", "PipelineId"},
|
||||
"AWS/Events": {"RuleName"},
|
||||
"AWS/FSx": {},
|
||||
"AWS/Firehose": {"DeliveryStreamName"},
|
||||
"AWS/Glue": {"JobName", "JobRunId", "Type"},
|
||||
"AWS/Inspector": {},
|
||||
"AWS/IoT": {"Protocol"},
|
||||
"AWS/KMS": {"KeyId"},
|
||||
"AWS/Kinesis": {"ShardId", "StreamName"},
|
||||
"AWS/KinesisAnalytics": {"Application", "Flow", "Id"},
|
||||
"AWS/KinesisVideo": {},
|
||||
"AWS/Lambda": {"Alias", "ExecutedVersion", "FunctionName", "Resource"},
|
||||
"AWS/Lex": {"BotAlias", "BotChannelName", "BotName", "BotVersion", "InputMode", "Operation", "Source"},
|
||||
"AWS/Logs": {"DestinationType", "FilterName", "LogGroupName"},
|
||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||
"AWS/MediaConvert": {"Job", "Operation", "Queue"},
|
||||
"AWS/MediaPackage": {"Channel", "No Dimension", "OriginEndpoint", "StatusCodeRange"},
|
||||
"AWS/MediaTailor": {"Configuration Name"},
|
||||
"AWS/NATGateway": {"NatGatewayId"},
|
||||
"AWS/Neptune": {"DBClusterIdentifier", "DatabaseClass", "EngineName", "Role"},
|
||||
"AWS/NetworkELB": {"AvailabilityZone", "LoadBalancer", "TargetGroup"},
|
||||
"AWS/OpsWorks": {"InstanceId", "LayerId", "StackId"},
|
||||
"AWS/Polly": {"Operation"},
|
||||
"AWS/RDS": {"DBClusterIdentifier", "DBInstanceIdentifier", "DatabaseClass", "DbClusterIdentifier", "EngineName", "Role", "SourceRegion"},
|
||||
"AWS/Redshift": {"ClusterIdentifier", "NodeID", "Service class", "Stage", "latency", "wmlid"},
|
||||
"AWS/Route53": {"HealthCheckId", "Region"},
|
||||
"AWS/S3": {"BucketName", "FilterId", "StorageType"},
|
||||
"AWS/SES": {},
|
||||
"AWS/SNS": {"Application", "Platform", "TopicName"},
|
||||
"AWS/SQS": {"QueueName"},
|
||||
"AWS/SWF": {"ActivityTypeName", "ActivityTypeVersion", "Domain", "WorkflowTypeName", "WorkflowTypeVersion"},
|
||||
"AWS/SageMaker": {"EndpointName", "Host", "LabelingJobName", "VariantName"},
|
||||
"AWS/States": {"APIName", "ActivityArn", "LambdaFunctionArn", "StateMachineArn", "StateTransition"},
|
||||
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
|
||||
"AWS/TransitGateway": {"TransitGateway"},
|
||||
"AWS/Translate": {"LanguagePair", "Operation"},
|
||||
"AWS/VPN": {"TunnelIpAddress", "VpnId"},
|
||||
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
|
||||
"Rekognition": {},
|
||||
"WAF": {"Region", "Rule", "RuleGroup", "WebACL"},
|
||||
}
|
||||
|
||||
customMetricsMetricsMap = make(map[string]map[string]map[string]*CustomMetricsCache)
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TagFilter } from './components/TagFilter/TagFilter';
|
||||
import { SideMenu } from './components/sidemenu/SideMenu';
|
||||
import { MetricSelect } from './components/Select/MetricSelect';
|
||||
import AppNotificationList from './components/AppNotifications/AppNotificationList';
|
||||
import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui';
|
||||
import { ColorPicker, SeriesColorPickerPopoverWithTheme, SecretFormField } from '@grafana/ui';
|
||||
import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
|
||||
|
||||
export function registerAngularDirectives() {
|
||||
@@ -59,4 +59,11 @@ export function registerAngularDirectives() {
|
||||
['datasource', { watchDepth: 'reference' }],
|
||||
['templateSrv', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('secretFormField', SecretFormField, [
|
||||
'value',
|
||||
'isConfigured',
|
||||
'inputWidth',
|
||||
['onReset', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, CSSProperties } from 'react';
|
||||
import Transition, { ExitHandler } from 'react-transition-group/Transition';
|
||||
|
||||
interface Props {
|
||||
@@ -10,12 +10,12 @@ interface Props {
|
||||
}
|
||||
|
||||
export const FadeIn: FC<Props> = props => {
|
||||
const defaultStyle = {
|
||||
const defaultStyle: CSSProperties = {
|
||||
transition: `opacity ${props.duration}ms linear`,
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
const transitionStyles = {
|
||||
const transitionStyles: { [str: string]: CSSProperties } = {
|
||||
exited: { opacity: 0, display: 'none' },
|
||||
entering: { opacity: 0 },
|
||||
entered: { opacity: 1 },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { CSSProperties, FC } from 'react';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
|
||||
interface Style {
|
||||
@@ -16,11 +16,18 @@ export const defaultStyle: Style = {
|
||||
overflow: 'hidden',
|
||||
};
|
||||
|
||||
export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = defaultStyle }) => {
|
||||
export interface Props {
|
||||
children: React.ReactNode;
|
||||
in: boolean;
|
||||
maxHeight?: number;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const SlideDown: FC<Props> = ({ children, in: inProp, maxHeight = defaultMaxHeight, style = defaultStyle }) => {
|
||||
// There are 4 main states a Transition can be in:
|
||||
// ENTERING, ENTERED, EXITING, EXITED
|
||||
// https://reactcommunity.org/react-transition-group/
|
||||
const transitionStyles = {
|
||||
// https://reactcommunity.or[g/react-transition-group/
|
||||
const transitionStyles: { [str: string]: CSSProperties } = {
|
||||
exited: { maxHeight: 0 },
|
||||
entering: { maxHeight: maxHeight },
|
||||
entered: { maxHeight: 'unset', overflow: 'visible' },
|
||||
@@ -34,6 +41,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
|
||||
style={{
|
||||
...style,
|
||||
...transitionStyles[state],
|
||||
inProp,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -11,10 +11,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export class CopyToClipboard extends PureComponent<Props> {
|
||||
clipboardjs: any;
|
||||
clipboardjs: ClipboardJS;
|
||||
myRef: any;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.myRef = React.createRef();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow } from 'enzyme';
|
||||
import EmptyListCTA from './EmptyListCTA';
|
||||
|
||||
const model = {
|
||||
@@ -16,7 +16,7 @@ const model = {
|
||||
|
||||
describe('EmptyListCTA', () => {
|
||||
it('renders correctly', () => {
|
||||
const tree = renderer.create(<EmptyListCTA model={model} />).toJSON();
|
||||
const tree = shallow(<EmptyListCTA model={model} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { PureComponent, createRef } from 'react';
|
||||
// import JSONFormatterJS, { JSONFormatterConfiguration } from 'json-formatter-js';
|
||||
import { JsonExplorer } from 'app/core/core'; // We have made some monkey-patching of json-formatter-js so we can't switch right now
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from '../../core_module';
|
||||
import { ISCEService, IQService } from 'angular';
|
||||
|
||||
function typeaheadMatcher(this: any, item) {
|
||||
function typeaheadMatcher(this: any, item: string) {
|
||||
let str = this.query;
|
||||
if (str === '') {
|
||||
return true;
|
||||
@@ -16,8 +17,8 @@ function typeaheadMatcher(this: any, item) {
|
||||
}
|
||||
|
||||
export class FormDropdownCtrl {
|
||||
inputElement: any;
|
||||
linkElement: any;
|
||||
inputElement: JQLite;
|
||||
linkElement: JQLite;
|
||||
model: any;
|
||||
display: any;
|
||||
text: any;
|
||||
@@ -37,7 +38,13 @@ export class FormDropdownCtrl {
|
||||
debounce: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope, $element, private $sce, private templateSrv, private $q) {
|
||||
constructor(
|
||||
private $scope: any,
|
||||
$element: JQLite,
|
||||
private $sce: ISCEService,
|
||||
private templateSrv: any,
|
||||
private $q: IQService
|
||||
) {
|
||||
this.inputElement = $element.find('input').first();
|
||||
this.linkElement = $element.find('a').first();
|
||||
this.linkMode = true;
|
||||
@@ -99,7 +106,7 @@ export class FormDropdownCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
getOptionsInternal(query) {
|
||||
getOptionsInternal(query: string) {
|
||||
const result = this.getOptions({ $query: query });
|
||||
if (this.isPromiseLike(result)) {
|
||||
return result;
|
||||
@@ -107,7 +114,7 @@ export class FormDropdownCtrl {
|
||||
return this.$q.when(result);
|
||||
}
|
||||
|
||||
isPromiseLike(obj) {
|
||||
isPromiseLike(obj: any) {
|
||||
return obj && typeof obj.then === 'function';
|
||||
}
|
||||
|
||||
@@ -117,7 +124,7 @@ export class FormDropdownCtrl {
|
||||
} else {
|
||||
// if we have text use it
|
||||
if (this.lookupText) {
|
||||
this.getOptionsInternal('').then(options => {
|
||||
this.getOptionsInternal('').then((options: any) => {
|
||||
const item = _.find(options, { value: this.model });
|
||||
this.updateDisplay(item ? item.text : this.model);
|
||||
});
|
||||
@@ -127,12 +134,12 @@ export class FormDropdownCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
typeaheadSource(query, callback) {
|
||||
this.getOptionsInternal(query).then(options => {
|
||||
typeaheadSource(query: string, callback: (res: any) => void) {
|
||||
this.getOptionsInternal(query).then((options: any) => {
|
||||
this.optionCache = options;
|
||||
|
||||
// extract texts
|
||||
const optionTexts = _.map(options, op => {
|
||||
const optionTexts = _.map(options, (op: any) => {
|
||||
return _.escape(op.text);
|
||||
});
|
||||
|
||||
@@ -147,7 +154,7 @@ export class FormDropdownCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
typeaheadUpdater(text) {
|
||||
typeaheadUpdater(text: string) {
|
||||
if (text === this.text) {
|
||||
clearTimeout(this.cancelBlur);
|
||||
this.inputElement.focus();
|
||||
@@ -159,7 +166,7 @@ export class FormDropdownCtrl {
|
||||
return text;
|
||||
}
|
||||
|
||||
switchToLink(fromClick) {
|
||||
switchToLink(fromClick: boolean) {
|
||||
if (this.linkMode && !fromClick) {
|
||||
return;
|
||||
}
|
||||
@@ -178,7 +185,7 @@ export class FormDropdownCtrl {
|
||||
this.cancelBlur = setTimeout(this.switchToLink.bind(this), 200);
|
||||
}
|
||||
|
||||
updateValue(text) {
|
||||
updateValue(text: string) {
|
||||
text = _.unescape(text);
|
||||
|
||||
if (text === '' || this.text === text) {
|
||||
@@ -214,7 +221,7 @@ export class FormDropdownCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
updateDisplay(text) {
|
||||
updateDisplay(text: string) {
|
||||
this.text = text;
|
||||
this.display = this.$sce.trustAsHtml(this.templateSrv.highlightVariablesAsHtml(text));
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ export class JsonExplorer {
|
||||
|
||||
// some pretty handling of number arrays
|
||||
if (this.isNumberArray()) {
|
||||
this.json.forEach((val, index) => {
|
||||
this.json.forEach((val: any, index: number) => {
|
||||
if (index > 0) {
|
||||
arrayWrapperSpan.appendChild(createElement('span', 'array-comma', ','));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export class LayoutSelectorCtrl {
|
||||
mode: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope) {
|
||||
constructor(private $rootScope: any) {
|
||||
this.mode = store.get('grafana.list.layout.mode') || 'grid';
|
||||
}
|
||||
|
||||
@@ -46,18 +46,18 @@ export function layoutSelector() {
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function layoutMode($rootScope) {
|
||||
export function layoutMode($rootScope: any) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {},
|
||||
link: (scope, elem) => {
|
||||
link: (scope: any, elem: any) => {
|
||||
const layout = store.get('grafana.list.layout.mode') || 'grid';
|
||||
let className = 'card-list-layout-' + layout;
|
||||
elem.addClass(className);
|
||||
|
||||
$rootScope.onAppEvent(
|
||||
'layout-mode-changed',
|
||||
(evt, newLayout) => {
|
||||
(evt: any, newLayout: any) => {
|
||||
elem.removeClass(className);
|
||||
className = 'card-list-layout-' + newLayout;
|
||||
elem.addClass(className);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// - reactComponent (generic directive for delegating off to React Components)
|
||||
// - reactDirective (factory for creating specific directives that correspond to reactComponent directives)
|
||||
|
||||
import { kebabCase } from 'lodash';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import angular from 'angular';
|
||||
@@ -155,11 +156,17 @@ function getPropExpression(prop) {
|
||||
return Array.isArray(prop) ? prop[0] : prop;
|
||||
}
|
||||
|
||||
// find the normalized attribute knowing that React props accept any type of capitalization
|
||||
function findAttribute(attrs, propName) {
|
||||
const index = Object.keys(attrs).filter(attr => {
|
||||
return attr.toLowerCase() === propName.toLowerCase();
|
||||
})[0];
|
||||
/**
|
||||
* Finds the normalized attribute knowing that React props accept any type of capitalization and it also handles
|
||||
* kabab case attributes which can be used in case the attribute would also be a standard html attribute and would be
|
||||
* evaluated by the browser as such.
|
||||
* @param attrs All attributes of the component.
|
||||
* @param propName Name of the prop that react component expects.
|
||||
*/
|
||||
function findAttribute(attrs: string, propName: string): string {
|
||||
const index = Object.keys(attrs).find(attr => {
|
||||
return attr.toLowerCase() === propName.toLowerCase() || attr.toLowerCase() === kebabCase(propName);
|
||||
});
|
||||
return attrs[index];
|
||||
}
|
||||
|
||||
@@ -274,7 +281,9 @@ const reactDirective = $injector => {
|
||||
// watch each property name and trigger an update whenever something changes,
|
||||
// to update scope.props with new values
|
||||
const propExpressions = props.map(prop => {
|
||||
return Array.isArray(prop) ? [attrs[getPropName(prop)], getPropConfig(prop)] : attrs[prop];
|
||||
return Array.isArray(prop)
|
||||
? [findAttribute(attrs, prop[0]), getPropConfig(prop)]
|
||||
: findAttribute(attrs, prop);
|
||||
});
|
||||
|
||||
// If we don't have any props, then our watch statement won't fire.
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import _ from 'lodash';
|
||||
import { Column, TableData } from '@grafana/ui';
|
||||
|
||||
interface Column {
|
||||
text: string;
|
||||
/**
|
||||
* Extends the standard Column class with variables that get
|
||||
* mutated in the angular table panel.
|
||||
*/
|
||||
interface MutableColumn extends Column {
|
||||
title?: string;
|
||||
type?: string;
|
||||
sort?: boolean;
|
||||
desc?: boolean;
|
||||
filterable?: boolean;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export default class TableModel {
|
||||
columns: Column[];
|
||||
export default class TableModel implements TableData {
|
||||
columns: MutableColumn[];
|
||||
rows: any[];
|
||||
type: string;
|
||||
columnMap: any;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
||||
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import { StoreState, FolderInfo } from 'app/types';
|
||||
import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
|
||||
import {
|
||||
|
||||
@@ -11,16 +11,15 @@ import {
|
||||
DataQueryResponse,
|
||||
DataQueryError,
|
||||
LoadingState,
|
||||
PanelData,
|
||||
TableData,
|
||||
TimeRange,
|
||||
TimeSeries,
|
||||
ScopedVars,
|
||||
toTableData,
|
||||
} from '@grafana/ui';
|
||||
|
||||
interface RenderProps {
|
||||
loading: LoadingState;
|
||||
panelData: PanelData;
|
||||
data: TableData[];
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
@@ -44,7 +43,7 @@ export interface State {
|
||||
isFirstLoad: boolean;
|
||||
loading: LoadingState;
|
||||
response: DataQueryResponse;
|
||||
panelData: PanelData;
|
||||
data?: TableData[];
|
||||
}
|
||||
|
||||
export class DataPanel extends Component<Props, State> {
|
||||
@@ -64,7 +63,6 @@ export class DataPanel extends Component<Props, State> {
|
||||
response: {
|
||||
data: [],
|
||||
},
|
||||
panelData: {},
|
||||
isFirstLoad: true,
|
||||
};
|
||||
}
|
||||
@@ -149,7 +147,7 @@ export class DataPanel extends Component<Props, State> {
|
||||
this.setState({
|
||||
loading: LoadingState.Done,
|
||||
response: resp,
|
||||
panelData: this.getPanelData(resp),
|
||||
data: toTableData(resp.data),
|
||||
isFirstLoad: false,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -172,23 +170,9 @@ export class DataPanel extends Component<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
getPanelData(response: DataQueryResponse) {
|
||||
if (response.data.length > 0 && (response.data[0] as TableData).type === 'table') {
|
||||
return {
|
||||
tableData: response.data[0] as TableData,
|
||||
timeSeries: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
timeSeries: response.data as TimeSeries[],
|
||||
tableData: null,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { queries } = this.props;
|
||||
const { loading, isFirstLoad, panelData } = this.state;
|
||||
const { loading, isFirstLoad, data } = this.state;
|
||||
|
||||
// do not render component until we have first data
|
||||
if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) {
|
||||
@@ -206,7 +190,7 @@ export class DataPanel extends Component<Props, State> {
|
||||
return (
|
||||
<>
|
||||
{loading === LoadingState.Loading && this.renderLoadingState()}
|
||||
{this.props.children({ loading, panelData })}
|
||||
{this.props.children({ loading, data })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { DataPanel } from './DataPanel';
|
||||
import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary';
|
||||
|
||||
// Utils
|
||||
import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel';
|
||||
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
||||
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
|
||||
import { profiler } from 'app/core/profiler';
|
||||
import config from 'app/core/config';
|
||||
@@ -19,7 +19,7 @@ import config from 'app/core/config';
|
||||
// Types
|
||||
import { DashboardModel, PanelModel } from '../state';
|
||||
import { PanelPlugin } from 'app/types';
|
||||
import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui';
|
||||
import { DataQueryResponse, TimeRange, LoadingState, TableData, DataQueryError, toTableData } from '@grafana/ui';
|
||||
import { ScopedVars } from '@grafana/ui';
|
||||
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
@@ -139,10 +139,10 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
get getDataForPanel() {
|
||||
return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null;
|
||||
return this.hasPanelSnapshot ? toTableData(this.props.panel.snapshotData) : null;
|
||||
}
|
||||
|
||||
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
|
||||
renderPanelPlugin(loading: LoadingState, data: TableData[], width: number, height: number): JSX.Element {
|
||||
const { panel, plugin } = this.props;
|
||||
const { timeRange, renderCounter } = this.state;
|
||||
const PanelComponent = plugin.exports.reactPanel.panel;
|
||||
@@ -157,7 +157,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
<div className="panel-content">
|
||||
<PanelComponent
|
||||
loading={loading}
|
||||
panelData={panelData}
|
||||
data={data}
|
||||
timeRange={timeRange}
|
||||
options={panel.getOptions(plugin.exports.reactPanel.defaults)}
|
||||
width={width - 2 * config.theme.panelPadding.horizontal}
|
||||
@@ -188,8 +188,8 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
onDataResponse={this.onDataResponse}
|
||||
onError={this.onDataError}
|
||||
>
|
||||
{({ loading, panelData }) => {
|
||||
return this.renderPanelPlugin(loading, panelData, width, height);
|
||||
{({ loading, data }) => {
|
||||
return this.renderPanelPlugin(loading, data, width, height);
|
||||
}}
|
||||
</DataPanel>
|
||||
) : (
|
||||
|
||||
@@ -111,12 +111,12 @@ export class PanelModel {
|
||||
cachedPluginOptions?: any;
|
||||
legend?: { show: boolean };
|
||||
|
||||
constructor(model) {
|
||||
constructor(model: any) {
|
||||
this.events = new Emitter();
|
||||
|
||||
// copy properties from persisted model
|
||||
for (const property in model) {
|
||||
this[property] = model[property];
|
||||
(this as any)[property] = model[property];
|
||||
}
|
||||
|
||||
// defaults
|
||||
@@ -150,7 +150,7 @@ export class PanelModel {
|
||||
}
|
||||
}
|
||||
|
||||
getOptions(panelDefaults) {
|
||||
getOptions(panelDefaults: any) {
|
||||
return _.defaultsDeep(this.options || {}, panelDefaults);
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ export class PanelModel {
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[property]: this[property],
|
||||
[property]: (this as any)[property],
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
@@ -236,7 +236,7 @@ export class PanelModel {
|
||||
const prevOptions = this.cachedPluginOptions[pluginId] || {};
|
||||
|
||||
Object.keys(prevOptions).map(property => {
|
||||
this[property] = prevOptions[property];
|
||||
(this as any)[property] = prevOptions[property];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ export class PanelModel {
|
||||
continue;
|
||||
}
|
||||
|
||||
delete this[key];
|
||||
delete (this as any)[key];
|
||||
}
|
||||
|
||||
this.cachedPluginOptions[oldPluginId] = oldOptions;
|
||||
|
||||
@@ -4,8 +4,7 @@ import store from 'app/core/store';
|
||||
// Models
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { PanelData, TimeRange, TimeSeries } from '@grafana/ui';
|
||||
import { TableData } from '@grafana/ui/src';
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
|
||||
// Utils
|
||||
import { isString as _isString } from 'lodash';
|
||||
@@ -170,19 +169,3 @@ export function getResolution(panel: PanelModel): number {
|
||||
|
||||
return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24));
|
||||
}
|
||||
|
||||
const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints');
|
||||
const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
|
||||
export const snapshotDataToPanelData = (panel: PanelModel): PanelData => {
|
||||
const snapshotData = panel.snapshotData;
|
||||
if (isTimeSeries(snapshotData[0])) {
|
||||
return {
|
||||
timeSeries: snapshotData,
|
||||
} as PanelData;
|
||||
} else if (isTableData(snapshotData[0])) {
|
||||
return {
|
||||
tableData: snapshotData[0],
|
||||
} as PanelData;
|
||||
}
|
||||
throw new Error('snapshotData is invalid:' + snapshotData.toString());
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { NavModel, StoreState, FolderState } from 'app/types';
|
||||
import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
|
||||
|
||||
@@ -41,7 +41,9 @@ export class PlaylistSrv {
|
||||
|
||||
const dash = this.dashboards[this.index];
|
||||
const queryParams = this.$location.search();
|
||||
const filteredParams = _.pickBy(queryParams, value => value !== null);
|
||||
const filteredParams = _.pickBy(queryParams, key => {
|
||||
return key === 'kiosk' || key === 'autofitpanels' || key === 'orgId';
|
||||
});
|
||||
const nextDashboardUrl = locationUtil.stripBaseFromUrl(dash.url);
|
||||
|
||||
// this is done inside timeout to make sure digest happens after
|
||||
|
||||
@@ -28,6 +28,7 @@ import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
|
||||
import * as singlestatPanel2 from 'app/plugins/panel/singlestat2/module';
|
||||
import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
|
||||
import * as gaugePanel from 'app/plugins/panel/gauge/module';
|
||||
import * as pieChartPanel from 'app/plugins/panel/piechart/module';
|
||||
import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
|
||||
|
||||
const builtInPlugins = {
|
||||
@@ -61,6 +62,7 @@ const builtInPlugins = {
|
||||
'app/plugins/panel/singlestat2/module': singlestatPanel2,
|
||||
'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
|
||||
'app/plugins/panel/gauge/module': gaugePanel,
|
||||
'app/plugins/panel/piechart/module': pieChartPanel,
|
||||
'app/plugins/panel/bargauge/module': barGaugePanel,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import { TeamGroup } from '../../types';
|
||||
import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { TeamMember, User } from 'app/types';
|
||||
|
||||
@@ -183,8 +183,9 @@ export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
|
||||
}
|
||||
|
||||
let paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
|
||||
const hasValue = paramValue !== null && paramValue !== undefined;
|
||||
|
||||
const last = index >= func.params.length - 1 && param.optional && !paramValue;
|
||||
const last = index >= func.params.length - 1 && param.optional && !hasValue;
|
||||
if (last && param.multiple) {
|
||||
paramValue = '+';
|
||||
}
|
||||
@@ -197,7 +198,7 @@ export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
|
||||
'<a ng-click="" class="graphite-func-param-link' +
|
||||
(last ? ' query-part__last' : '') +
|
||||
'">' +
|
||||
(paramValue || ' ') +
|
||||
(hasValue ? paramValue : ' ') +
|
||||
'</a>'
|
||||
);
|
||||
const $input = $(paramTemplate);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import TableModel from 'app/core/table_model';
|
||||
import { ColumnType } from '@grafana/ui';
|
||||
|
||||
export default class InfluxSeries {
|
||||
series: any;
|
||||
@@ -156,7 +157,7 @@ export default class InfluxSeries {
|
||||
// Check that the first column is indeed 'time'
|
||||
if (series.columns[0] === 'time') {
|
||||
// Push this now before the tags and with the right type
|
||||
table.columns.push({ text: 'Time', type: 'time' });
|
||||
table.columns.push({ text: 'Time', type: ColumnType.time });
|
||||
j++;
|
||||
}
|
||||
_.each(_.keys(series.tags), key => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
export class MssqlConfigCtrl {
|
||||
static templateUrl = 'partials/config.html';
|
||||
|
||||
@@ -7,4 +9,16 @@ export class MssqlConfigCtrl {
|
||||
constructor($scope) {
|
||||
this.current.jsonData.encrypt = this.current.jsonData.encrypt || 'false';
|
||||
}
|
||||
|
||||
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
this.current.secureJsonFields.password = false;
|
||||
this.current.secureJsonData = this.current.secureJsonData || {};
|
||||
this.current.secureJsonData.password = '';
|
||||
};
|
||||
|
||||
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
|
||||
this.current.secureJsonData = this.current.secureJsonData || {};
|
||||
this.current.secureJsonData.password = event.currentTarget.value;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
<span class="gf-form-label width-7">User</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder="user"></input>
|
||||
</div>
|
||||
<div class="gf-form max-width-15" ng-if="!ctrl.current.secureJsonFields.password">
|
||||
<span class="gf-form-label width-7">Password</span>
|
||||
<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>
|
||||
</div>
|
||||
<div class="gf-form max-width-19" ng-if="ctrl.current.secureJsonFields.password">
|
||||
<span class="gf-form-label width-7">Password</span>
|
||||
<input type="text" class="gf-form-input" disabled="disabled" value="configured">
|
||||
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.current.secureJsonFields.password = false">reset</a>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<secret-form-field
|
||||
isConfigured="ctrl.current.secureJsonFields.password"
|
||||
value="ctrl.current.secureJsonData.password"
|
||||
on-reset="ctrl.onPasswordReset"
|
||||
on-change="ctrl.onPasswordChange"
|
||||
inputWidth="9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
export class PostgresConfigCtrl {
|
||||
static templateUrl = 'partials/config.html';
|
||||
@@ -52,6 +53,18 @@ export class PostgresConfigCtrl {
|
||||
this.showTimescaleDBHelp = !this.showTimescaleDBHelp;
|
||||
}
|
||||
|
||||
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
this.current.secureJsonFields.password = false;
|
||||
this.current.secureJsonData = this.current.secureJsonData || {};
|
||||
this.current.secureJsonData.password = '';
|
||||
};
|
||||
|
||||
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
|
||||
this.current.secureJsonData = this.current.secureJsonData || {};
|
||||
this.current.secureJsonData.password = event.currentTarget.value;
|
||||
};
|
||||
|
||||
// the value portion is derived from postgres server_version_num/100
|
||||
postgresVersions = [
|
||||
{ name: '9.3', value: 903 },
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
<span class="gf-form-label width-7">User</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder="user"></input>
|
||||
</div>
|
||||
<div class="gf-form max-width-15" ng-if="!ctrl.current.secureJsonFields.password">
|
||||
<span class="gf-form-label width-7">Password</span>
|
||||
<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>
|
||||
</div>
|
||||
<div class="gf-form max-width-19" ng-if="ctrl.current.secureJsonFields.password">
|
||||
<span class="gf-form-label width-7">Password</span>
|
||||
<input type="text" class="gf-form-input" disabled="disabled" value="configured">
|
||||
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.current.secureJsonFields.password = false">reset</a>
|
||||
<div class="gf-form">
|
||||
<secret-form-field
|
||||
isConfigured="ctrl.current.secureJsonFields.password"
|
||||
value="ctrl.current.secureJsonData.password"
|
||||
on-reset="ctrl.onPasswordReset"
|
||||
on-change="ctrl.onPasswordChange"
|
||||
inputWidth="9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">SSL Mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-15 gf-form-select-wrapper--has-help-icon">
|
||||
|
||||
@@ -224,7 +224,8 @@ export class PrometheusDatasource implements DataSourceApi<PromQuery> {
|
||||
query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr);
|
||||
query.requestId = options.panelId + target.refId;
|
||||
|
||||
// Align query interval with step
|
||||
// Align query interval with step to allow query caching and to ensure
|
||||
// that about-same-time query results look the same.
|
||||
const adjusted = alignRange(start, end, query.step);
|
||||
query.start = adjusted.start;
|
||||
query.end = adjusted.end;
|
||||
@@ -497,8 +498,15 @@ export class PrometheusDatasource implements DataSourceApi<PromQuery> {
|
||||
}
|
||||
}
|
||||
|
||||
export function alignRange(start, end, step) {
|
||||
const alignedEnd = Math.ceil(end / step) * step;
|
||||
/**
|
||||
* Align query range to step.
|
||||
* Rounds start and end down to a multiple of step.
|
||||
* @param start Timestamp marking the beginning of the range.
|
||||
* @param end Timestamp marking the end of the range.
|
||||
* @param step Interval to align start and end with.
|
||||
*/
|
||||
export function alignRange(start: number, end: number, step: number): { end: number; start: number } {
|
||||
const alignedEnd = Math.floor(end / step) * step;
|
||||
const alignedStart = Math.floor(start / step) * step;
|
||||
return {
|
||||
end: alignedEnd,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import TableModel from 'app/core/table_model';
|
||||
import { TimeSeries } from '@grafana/ui';
|
||||
import { TimeSeries, ColumnType } from '@grafana/ui';
|
||||
|
||||
export class ResultTransformer {
|
||||
constructor(private templateSrv) {}
|
||||
@@ -98,7 +98,7 @@ export class ResultTransformer {
|
||||
|
||||
// Sort metric labels, create columns for them and record their index
|
||||
const sortedLabels = _.keys(metricLabels).sort();
|
||||
table.columns.push({ text: 'Time', type: 'time' });
|
||||
table.columns.push({ text: 'Time', type: ColumnType.time });
|
||||
_.each(sortedLabels, (label, labelIndex) => {
|
||||
metricLabels[label] = labelIndex + 1;
|
||||
table.columns.push({ text: label, filterable: true });
|
||||
|
||||
@@ -206,12 +206,12 @@ describe('PrometheusDatasource', () => {
|
||||
it('does align intervals that are a multiple of steps', () => {
|
||||
const range = alignRange(1, 4, 3);
|
||||
expect(range.start).toEqual(0);
|
||||
expect(range.end).toEqual(6);
|
||||
expect(range.end).toEqual(3);
|
||||
});
|
||||
it('does align intervals that are not a multiple of steps', () => {
|
||||
const range = alignRange(1, 5, 3);
|
||||
expect(range.start).toEqual(0);
|
||||
expect(range.end).toEqual(6);
|
||||
expect(range.end).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -351,7 +351,7 @@ const timeSrv = {
|
||||
};
|
||||
|
||||
describe('PrometheusDatasource', () => {
|
||||
describe('When querying prometheus with one target using query editor target spec', async () => {
|
||||
describe('When querying prometheus with one target using query editor target spec', () => {
|
||||
let results;
|
||||
const query = {
|
||||
range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) },
|
||||
@@ -360,7 +360,7 @@ describe('PrometheusDatasource', () => {
|
||||
};
|
||||
// Interval alignment with step
|
||||
const urlExpected =
|
||||
'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=240&step=60';
|
||||
'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=180&step=60';
|
||||
|
||||
beforeEach(async () => {
|
||||
const response = {
|
||||
@@ -788,7 +788,7 @@ describe('PrometheusDatasource', () => {
|
||||
interval: '5s',
|
||||
};
|
||||
// times get rounded up to interval
|
||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=450&step=50';
|
||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50';
|
||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
||||
await ctx.ds.query(query);
|
||||
@@ -831,7 +831,7 @@ describe('PrometheusDatasource', () => {
|
||||
interval: '10s',
|
||||
};
|
||||
// times get aligned to interval
|
||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=500&step=100';
|
||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100';
|
||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
||||
await ctx.ds.query(query);
|
||||
@@ -996,7 +996,7 @@ describe('PrometheusDatasource', () => {
|
||||
const urlExpected =
|
||||
'proxied/api/v1/query_range?query=' +
|
||||
encodeURIComponent('rate(test[$__interval])') +
|
||||
'&start=0&end=500&step=100';
|
||||
'&start=0&end=400&step=100';
|
||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||
templateSrv.replace = jest.fn(str => str);
|
||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
||||
@@ -1041,7 +1041,7 @@ describe('PrometheusDatasource', () => {
|
||||
const urlExpected =
|
||||
'proxied/api/v1/query_range?query=' +
|
||||
encodeURIComponent('rate(test[$__interval])') +
|
||||
'&start=50&end=450&step=50';
|
||||
'&start=50&end=400&step=50';
|
||||
|
||||
templateSrv.replace = jest.fn(str => str);
|
||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||
@@ -1166,7 +1166,7 @@ describe('PrometheusDatasource for POST', () => {
|
||||
const dataExpected = {
|
||||
query: 'test{job="testjob"}',
|
||||
start: 1 * 60,
|
||||
end: 3 * 60,
|
||||
end: 2 * 60,
|
||||
step: 60,
|
||||
};
|
||||
const query = {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import TableModel from 'app/core/table_model';
|
||||
import { DataSourceApi, DataQueryOptions } from '@grafana/ui';
|
||||
import { DataSourceApi, DataQueryOptions, TableData, TimeSeries } from '@grafana/ui';
|
||||
import { TestDataQuery, Scenario } from './types';
|
||||
|
||||
type TestData = TimeSeries | TableData;
|
||||
|
||||
export interface TestDataRegistry {
|
||||
[key: string]: TestData[];
|
||||
}
|
||||
|
||||
export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
|
||||
id: number;
|
||||
|
||||
@@ -42,26 +47,24 @@ export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
const data = [];
|
||||
const data: TestData[] = [];
|
||||
|
||||
if (res.data.results) {
|
||||
_.forEach(res.data.results, queryRes => {
|
||||
if (queryRes.tables) {
|
||||
for (const table of queryRes.tables) {
|
||||
const model = new TableModel();
|
||||
model.rows = table.rows;
|
||||
model.columns = table.columns;
|
||||
// Returns data in the order it was asked for.
|
||||
// if the response has data with different refId, it is ignored
|
||||
for (const query of queries) {
|
||||
const results = res.data.results[query.refId];
|
||||
if (!results) {
|
||||
console.warn('No Results for:', query);
|
||||
continue;
|
||||
}
|
||||
|
||||
data.push(model);
|
||||
}
|
||||
}
|
||||
for (const series of queryRes.series) {
|
||||
data.push({
|
||||
target: series.name,
|
||||
datapoints: series.points,
|
||||
});
|
||||
}
|
||||
});
|
||||
for (const table of results.tables || []) {
|
||||
data.push(table as TableData);
|
||||
}
|
||||
|
||||
for (const series of results.series || []) {
|
||||
data.push({ target: series.name, datapoints: series.points });
|
||||
}
|
||||
}
|
||||
|
||||
return { data: data };
|
||||
|
||||
@@ -32,14 +32,14 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { height, width, options, panelData, renderCounter } = this.props;
|
||||
const { height, width, options, data, renderCounter } = this.props;
|
||||
return (
|
||||
<ProcessedValuesRepeater
|
||||
getProcessedValues={this.getProcessedValues}
|
||||
renderValue={this.renderValue}
|
||||
width={width}
|
||||
height={height}
|
||||
source={panelData}
|
||||
source={data}
|
||||
renderCounter={renderCounter}
|
||||
orientation={options.orientation}
|
||||
/>
|
||||
|
||||
@@ -37,14 +37,14 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { height, width, options, panelData, renderCounter } = this.props;
|
||||
const { height, width, options, data, renderCounter } = this.props;
|
||||
return (
|
||||
<ProcessedValuesRepeater
|
||||
getProcessedValues={this.getProcessedValues}
|
||||
renderValue={this.renderValue}
|
||||
width={width}
|
||||
height={height}
|
||||
source={panelData}
|
||||
source={data}
|
||||
renderCounter={renderCounter}
|
||||
orientation={options.orientation}
|
||||
/>
|
||||
|
||||
@@ -16,13 +16,13 @@ interface Props extends PanelProps<Options> {}
|
||||
|
||||
export class GraphPanel extends PureComponent<Props> {
|
||||
render() {
|
||||
const { panelData, timeRange, width, height } = this.props;
|
||||
const { data, timeRange, width, height } = this.props;
|
||||
const { showLines, showBars, showPoints } = this.props.options;
|
||||
|
||||
let vmSeries: TimeSeriesVMs;
|
||||
if (panelData.timeSeries) {
|
||||
if (data) {
|
||||
vmSeries = processTimeSeries({
|
||||
timeSeries: panelData.timeSeries,
|
||||
data,
|
||||
nullValueMode: NullValueMode.Ignore,
|
||||
});
|
||||
}
|
||||
|
||||
47
public/app/plugins/panel/piechart/PieChartOptionsBox.tsx
Normal file
47
public/app/plugins/panel/piechart/PieChartOptionsBox.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Select, FormLabel, PanelOptionsGroup } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { FormField, PanelEditorProps } from '@grafana/ui';
|
||||
import { PieChartType } from '@grafana/ui';
|
||||
import { PieChartOptions } from './types';
|
||||
|
||||
const labelWidth = 8;
|
||||
|
||||
const pieChartOptions = [{ value: PieChartType.PIE, label: 'Pie' }, { value: PieChartType.DONUT, label: 'Donut' }];
|
||||
|
||||
export class PieChartOptionsBox extends PureComponent<PanelEditorProps<PieChartOptions>> {
|
||||
onPieTypeChange = pieType => this.props.onOptionsChange({ ...this.props.options, pieType: pieType.value });
|
||||
onStrokeWidthChange = ({ target }) =>
|
||||
this.props.onOptionsChange({ ...this.props.options, strokeWidth: target.value });
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { pieType, strokeWidth } = options;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="PieChart">
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Type</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={pieChartOptions}
|
||||
onChange={this.onPieTypeChange}
|
||||
value={pieChartOptions.find(option => option.value === pieType)}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
label="Divider width"
|
||||
labelWidth={labelWidth}
|
||||
onChange={this.onStrokeWidthChange}
|
||||
value={strokeWidth}
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
57
public/app/plugins/panel/piechart/PieChartPanel.tsx
Normal file
57
public/app/plugins/panel/piechart/PieChartPanel.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Services & Utils
|
||||
import { processTimeSeries, ThemeContext } from '@grafana/ui';
|
||||
|
||||
// Components
|
||||
import { PieChart, PieChartDataPoint } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PieChartOptions } from './types';
|
||||
import { PanelProps, NullValueMode } from '@grafana/ui/src/types';
|
||||
|
||||
interface Props extends PanelProps<PieChartOptions> {}
|
||||
|
||||
export class PieChartPanel extends PureComponent<Props> {
|
||||
render() {
|
||||
const { data, width, height, options } = this.props;
|
||||
const { valueOptions } = options;
|
||||
|
||||
const datapoints: PieChartDataPoint[] = [];
|
||||
if (data) {
|
||||
const vmSeries = processTimeSeries({
|
||||
data,
|
||||
nullValueMode: NullValueMode.Null,
|
||||
});
|
||||
|
||||
for (let i = 0; i < vmSeries.length; i++) {
|
||||
const serie = vmSeries[i];
|
||||
if (serie) {
|
||||
datapoints.push({
|
||||
value: serie.stats[valueOptions.stat],
|
||||
name: serie.label,
|
||||
color: serie.color,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: support table data
|
||||
|
||||
return (
|
||||
<ThemeContext.Consumer>
|
||||
{theme => (
|
||||
<PieChart
|
||||
width={width}
|
||||
height={height}
|
||||
datapoints={datapoints}
|
||||
pieType={options.pieType}
|
||||
strokeWidth={options.strokeWidth}
|
||||
unit={valueOptions.unit}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</ThemeContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
27
public/app/plugins/panel/piechart/PieChartPanelEditor.tsx
Normal file
27
public/app/plugins/panel/piechart/PieChartPanelEditor.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PanelEditorProps, PanelOptionsGrid } from '@grafana/ui';
|
||||
|
||||
import PieChartValueEditor from './PieChartValueEditor';
|
||||
import { PieChartOptionsBox } from './PieChartOptionsBox';
|
||||
import { PieChartOptions, PieChartValueOptions } from './types';
|
||||
|
||||
export default class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> {
|
||||
onValueOptionsChanged = (valueOptions: PieChartValueOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
valueOptions,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { onOptionsChange, options } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PieChartValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
|
||||
<PieChartOptionsBox onOptionsChange={onOptionsChange} options={options} />
|
||||
</PanelOptionsGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
54
public/app/plugins/panel/piechart/PieChartValueEditor.tsx
Normal file
54
public/app/plugins/panel/piechart/PieChartValueEditor.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui';
|
||||
import { PieChartValueOptions } from './types';
|
||||
|
||||
const statOptions = [
|
||||
{ value: 'min', label: 'Min' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
{ value: 'avg', label: 'Average' },
|
||||
{ value: 'current', label: 'Current' },
|
||||
{ value: 'total', label: 'Total' },
|
||||
];
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
export interface Props {
|
||||
options: PieChartValueOptions;
|
||||
onChange: (valueOptions: PieChartValueOptions) => void;
|
||||
}
|
||||
|
||||
export default class PieChartValueEditor extends PureComponent<Props> {
|
||||
onUnitChange = unit =>
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
unit: unit.value,
|
||||
});
|
||||
|
||||
onStatChange = stat =>
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
stat: stat.value,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { stat, unit } = this.props.options;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Value">
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Value</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={statOptions}
|
||||
onChange={this.onStatChange}
|
||||
value={statOptions.find(option => option.value === stat)}
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
public/app/plugins/panel/piechart/img/icon_piechart.svg
Normal file
29
public/app/plugins/panel/piechart/img/icon_piechart.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#C9202F;}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
</style>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="34.3609" y1="59.9311" x2="106.9924" y2="-19.0856">
|
||||
<stop offset="0" style="stop-color:#FFF33B"/>
|
||||
<stop offset="5.948725e-02" style="stop-color:#FFE029"/>
|
||||
<stop offset="0.1303" style="stop-color:#FFD218"/>
|
||||
<stop offset="0.2032" style="stop-color:#FEC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC70C"/>
|
||||
<stop offset="0.6685" style="stop-color:#F3903F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED683C"/>
|
||||
<stop offset="1" style="stop-color:#E93E3A"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M51.8,0.1v47.4l45.1-14.7C89.8,13.4,72.4,0.8,51.8,0.1z"/>
|
||||
<path class="st1" d="M98,36.3L52.9,50.9l17.7,24.3l10.2,14C97.1,76.6,103.7,56.1,98,36.3z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="1.519853e-02" y1="50.001" x2="77.8424" y2="50.001">
|
||||
<stop offset="0" style="stop-color:#04A64D"/>
|
||||
<stop offset="1" style="stop-color:#007E39"/>
|
||||
</linearGradient>
|
||||
<path class="st2" d="M48.2,50.6V0.1C21.4,1,0,23,0,50C0,77.5,22.4,99.9,50,99.9c10.5,0,19.4-2.7,27.9-8.5L48.2,50.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
10
public/app/plugins/panel/piechart/module.tsx
Normal file
10
public/app/plugins/panel/piechart/module.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ReactPanelPlugin } from '@grafana/ui';
|
||||
|
||||
import PieChartPanelEditor from './PieChartPanelEditor';
|
||||
import { PieChartPanel } from './PieChartPanel';
|
||||
import { PieChartOptions, defaults } from './types';
|
||||
|
||||
export const reactPanel = new ReactPanelPlugin<PieChartOptions>(PieChartPanel);
|
||||
|
||||
reactPanel.setEditor(PieChartPanelEditor);
|
||||
reactPanel.setDefaults(defaults);
|
||||
19
public/app/plugins/panel/piechart/plugin.json
Normal file
19
public/app/plugins/panel/piechart/plugin.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "PieChart v2",
|
||||
"id": "piechart",
|
||||
"state": "alpha",
|
||||
|
||||
"dataFormats": ["time_series"],
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icon_piechart.svg",
|
||||
"large": "img/icon_piechart.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
public/app/plugins/panel/piechart/types.ts
Normal file
21
public/app/plugins/panel/piechart/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { PieChartType } from '@grafana/ui';
|
||||
|
||||
export interface PieChartOptions {
|
||||
pieType: PieChartType;
|
||||
strokeWidth: number;
|
||||
valueOptions: PieChartValueOptions;
|
||||
}
|
||||
|
||||
export interface PieChartValueOptions {
|
||||
unit: string;
|
||||
stat: string;
|
||||
}
|
||||
|
||||
export const defaults: PieChartOptions = {
|
||||
pieType: PieChartType.PIE,
|
||||
strokeWidth: 1,
|
||||
valueOptions: {
|
||||
unit: 'short',
|
||||
stat: 'current',
|
||||
},
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn';
|
||||
import config from 'app/core/config';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||
import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
|
||||
import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName, isTableData } from '@grafana/ui';
|
||||
|
||||
class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
@@ -112,7 +112,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
scopedVars: _.extend({}, this.panel.scopedVars),
|
||||
};
|
||||
|
||||
if (dataList.length > 0 && dataList[0].type === 'table') {
|
||||
if (dataList.length > 0 && isTableData(dataList[0])) {
|
||||
this.dataType = 'table';
|
||||
const tableData = dataList.map(this.tableHandler.bind(this));
|
||||
this.setTableValues(tableData, data);
|
||||
|
||||
@@ -4,28 +4,33 @@ import React, { PureComponent, CSSProperties } from 'react';
|
||||
// Types
|
||||
import { SingleStatOptions, SingleStatBaseOptions } from './types';
|
||||
|
||||
import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui';
|
||||
import { DisplayValue, PanelProps, processTimeSeries, NullValueMode } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { getDisplayProcessor } from '@grafana/ui';
|
||||
import { ProcessedValuesRepeater } from './ProcessedValuesRepeater';
|
||||
|
||||
export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): DisplayValue[] => {
|
||||
const { panelData, replaceVariables, options } = props;
|
||||
const { data, replaceVariables, options } = props;
|
||||
const { valueOptions, valueMappings } = options;
|
||||
const { unit, decimals, stat } = valueOptions;
|
||||
|
||||
const processor = getDisplayProcessor({
|
||||
unit: valueOptions.unit,
|
||||
decimals: valueOptions.decimals,
|
||||
unit,
|
||||
decimals,
|
||||
mappings: valueMappings,
|
||||
thresholds: options.thresholds,
|
||||
|
||||
prefix: replaceVariables(valueOptions.prefix),
|
||||
suffix: replaceVariables(valueOptions.suffix),
|
||||
theme: config.theme,
|
||||
});
|
||||
return processSingleStatPanelData({
|
||||
panelData: panelData,
|
||||
stat: valueOptions.stat,
|
||||
}).map(stat => processor(stat.value));
|
||||
|
||||
return processTimeSeries({
|
||||
data,
|
||||
nullValueMode: NullValueMode.Null,
|
||||
}).map((series, index) => {
|
||||
const value = stat !== 'name' ? series.stats[stat] : series.label;
|
||||
return processor(value);
|
||||
});
|
||||
};
|
||||
|
||||
export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>> {
|
||||
@@ -50,14 +55,14 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
|
||||
};
|
||||
|
||||
render() {
|
||||
const { height, width, options, panelData, renderCounter } = this.props;
|
||||
const { height, width, options, data, renderCounter } = this.props;
|
||||
return (
|
||||
<ProcessedValuesRepeater
|
||||
getProcessedValues={this.getProcessedValues}
|
||||
renderValue={this.renderValue}
|
||||
width={width}
|
||||
height={height}
|
||||
source={panelData}
|
||||
source={data}
|
||||
renderCounter={renderCounter}
|
||||
orientation={options.orientation}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { transformDataToTable } from './transformers';
|
||||
import { tablePanelEditor } from './editor';
|
||||
import { columnOptionsTab } from './column_options';
|
||||
import { TableRenderer } from './renderer';
|
||||
import { isTableData } from '@grafana/ui';
|
||||
|
||||
class TablePanelCtrl extends MetricsPanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
@@ -104,7 +105,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
||||
|
||||
// automatically correct transform mode based on data
|
||||
if (this.dataRaw && this.dataRaw.length) {
|
||||
if (this.dataRaw[0].type === 'table') {
|
||||
if (isTableData(this.dataRaw[0])) {
|
||||
this.panel.transform = 'table';
|
||||
} else {
|
||||
if (this.dataRaw[0].type === 'docs') {
|
||||
|
||||
@@ -14,15 +14,15 @@ export class TablePanel extends Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panelData, options } = this.props;
|
||||
const { data, options } = this.props;
|
||||
|
||||
if (!panelData || !panelData.tableData) {
|
||||
if (data.length < 1) {
|
||||
return <div>No Table Data...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Consumer>
|
||||
{theme => <Table {...this.props} {...options} theme={theme} data={panelData.tableData} />}
|
||||
{theme => <Table {...this.props} {...options} theme={theme} data={data[0]} />}
|
||||
</ThemeContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
@import 'components/panel_alertlist';
|
||||
@import 'components/panel_dashlist';
|
||||
@import 'components/panel_gettingstarted';
|
||||
@import 'components/panel_piechart';
|
||||
@import 'components/panel_pluginlist';
|
||||
@import 'components/panel_singlestat';
|
||||
@import 'components/panel_table';
|
||||
|
||||
@@ -90,7 +90,7 @@ $grid-gutter-width: 30px !default;
|
||||
// Typography
|
||||
// -------------------------
|
||||
|
||||
$font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif;
|
||||
$font-family-sans-serif: 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
||||
$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
|
||||
$font-size-root: 14px !default;
|
||||
@@ -113,7 +113,6 @@ $font-size-h4: 18px !default;
|
||||
$font-size-h5: 16px !default;
|
||||
$font-size-h6: 14px !default;
|
||||
|
||||
$headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$headings-line-height: 1.1 !default;
|
||||
|
||||
// Components
|
||||
@@ -200,10 +199,8 @@ $btn-semi-transparent: rgba(0, 0, 0, 0.2) !default;
|
||||
$side-menu-width: 60px;
|
||||
|
||||
// dashboard
|
||||
$dashboard-padding: 10px * 2;
|
||||
$panel-horizontal-padding: 10;
|
||||
$panel-vertical-padding: 5;
|
||||
$panel-padding: 0px $panel-horizontal-padding + 0px $panel-vertical-padding + 0px $panel-horizontal-padding + 0px;
|
||||
$dashboard-padding: $space-md;
|
||||
$panel-padding: 0 $space-md $space-sm $space-md;
|
||||
|
||||
// tabs
|
||||
$tabs-padding: 10px 15px 9px;
|
||||
|
||||
@@ -110,7 +110,6 @@ h6,
|
||||
.h5,
|
||||
.h6 {
|
||||
margin-bottom: $space-sm;
|
||||
font-family: $headings-font-family;
|
||||
font-weight: $font-weight-regular;
|
||||
line-height: $headings-line-height;
|
||||
color: $headings-color;
|
||||
|
||||
@@ -83,10 +83,6 @@
|
||||
padding: 0 $dashboard-padding $space-sm $dashboard-padding;
|
||||
}
|
||||
|
||||
.panel-editor-container__panel {
|
||||
margin: 0 $dashboard-padding;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ $column-horizontal-spacing: 10px;
|
||||
.logs-panel-options {
|
||||
display: flex;
|
||||
background-color: $page-bg;
|
||||
padding: $panel-padding;
|
||||
padding-top: 10px;
|
||||
padding: $space-sm $space-md $space-sm $space-md;
|
||||
border-radius: $border-radius;
|
||||
margin: $space-md 0 $space-sm;
|
||||
border: $panel-border;
|
||||
|
||||
40
public/sass/components/_panel_piechart.scss
Normal file
40
public/sass/components/_panel_piechart.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
.piechart-panel {
|
||||
position: relative;
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.piechart-container {
|
||||
top: 10px;
|
||||
margin: auto;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.piechart-tooltip {
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
background-color: #141414;
|
||||
color: #d8d9da;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
.piechart-tooltip-time {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 0.2rem;
|
||||
font-weight: bold;
|
||||
color: #d8d9da;
|
||||
|
||||
.piechart-tooltip-value {
|
||||
display: table-cell;
|
||||
font-weight: bold;
|
||||
padding: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
.tabbed-view-header {
|
||||
box-shadow: $page-header-shadow;
|
||||
border-bottom: 1px solid $page-header-border-color;
|
||||
padding: 0 $dashboard-padding;
|
||||
padding: 0 $space-md;
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,6 @@ div.flot-text {
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
font-family: $headings-font-family;
|
||||
font-size: $font-size-h3;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
@@ -273,10 +272,6 @@ div.flot-text {
|
||||
}
|
||||
}
|
||||
|
||||
.panel-full-edit {
|
||||
padding-top: $dashboard-padding;
|
||||
}
|
||||
|
||||
.dashboard-loading {
|
||||
height: 60vh;
|
||||
display: flex;
|
||||
|
||||
@@ -176,12 +176,10 @@
|
||||
}
|
||||
|
||||
.explore-panel__header {
|
||||
padding: $panel-padding;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0;
|
||||
padding: $space-sm $space-md 0 $space-md;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: $space-sm;
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ module.exports = function(options) {
|
||||
options: {
|
||||
importLoaders: 2,
|
||||
url: options.preserveUrl,
|
||||
sourceMap: options.sourceMap,
|
||||
minimize: options.minimize,
|
||||
sourceMap: options.sourceMap
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,13 +19,6 @@ module.exports = merge(common, {
|
||||
light: './public/sass/grafana.light.scss',
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../../public/build'),
|
||||
filename: '[name].[hash].js',
|
||||
// Keep publicPath relative for host.com/grafana/ deployments
|
||||
publicPath: "public/build/",
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -50,7 +43,7 @@ module.exports = merge(common, {
|
||||
},
|
||||
},
|
||||
},
|
||||
require('./sass.rule.js')({ sourceMap: false, minimize: false, preserveUrl: false }),
|
||||
require('./sass.rule.js')({ sourceMap: false, preserveUrl: false }),
|
||||
{
|
||||
test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
||||
loader: 'file-loader'
|
||||
@@ -59,7 +52,7 @@ module.exports = merge(common, {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanWebpackPlugin('../../public/build', { allowExternal: true }),
|
||||
new CleanWebpackPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "grafana.[name].[hash].css"
|
||||
}),
|
||||
|
||||
@@ -56,7 +56,7 @@ module.exports = merge(common, {
|
||||
plugins: [
|
||||
[require('@rtsao/plugin-proposal-class-properties'), { loose: true }],
|
||||
'angularjs-annotate',
|
||||
'syntax-dynamic-import', // needed for `() => import()` in routes.ts
|
||||
'@babel/plugin-syntax-dynamic-import', // needed for `() => import()` in routes.ts
|
||||
'react-hot-loader/babel',
|
||||
],
|
||||
presets: [
|
||||
@@ -98,7 +98,7 @@ module.exports = merge(common, {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanWebpackPlugin('../public/build', { allowExternal: true }),
|
||||
new CleanWebpackPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const common = require('./webpack.common.js');
|
||||
const path = require('path');
|
||||
const ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
|
||||
@@ -43,14 +43,14 @@ module.exports = merge(common, {
|
||||
},
|
||||
},
|
||||
require('./sass.rule.js')({
|
||||
sourceMap: false, minimize: false, preserveUrl: false
|
||||
sourceMap: false, preserveUrl: false
|
||||
})
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new UglifyJsPlugin({
|
||||
cache: true,
|
||||
new TerserPlugin({
|
||||
cache: false,
|
||||
parallel: true,
|
||||
sourceMap: true
|
||||
}),
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"baseUrl": "public",
|
||||
"pretty": true,
|
||||
"typeRoots": ["node_modules/@types", "types"],
|
||||
"typeRoots": ["node_modules/@types", "public/app/types"],
|
||||
"paths": {
|
||||
"app": ["app"],
|
||||
"sass": ["sass"]
|
||||
|
||||
Reference in New Issue
Block a user