mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 08:02:25 +08:00
PackageJson: Prettify markdown/mdx on commit with lint-staged (#37616)
* Format md,mdx files with prettier on lint-staged * Manually run prettier on docs/sources
This commit is contained in:
@ -83,5 +83,3 @@ Learn more about Grafana options and packages.
|
||||
#### Go
|
||||
|
||||
- [Grafana Plugin SDK for Go]({{< relref "backend/grafana-plugin-sdk-for-go" >}})
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ title = "Add authentication for data source plugins"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/auth-for-datasources/", "/docs/grafana/next/developers/plugins/authentication/"]
|
||||
+++
|
||||
|
||||
# Add authentication for data source plugins
|
||||
# Add authentication for data source plugins
|
||||
|
||||
This page explains how to configure your data source plugin to authenticate against a third-party API.
|
||||
|
||||
@ -146,11 +146,10 @@ To forward requests through the Grafana proxy, you need to configure one or more
|
||||
```ts
|
||||
const routePath = '/example';
|
||||
|
||||
getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: this.url + routePath + '/v1/users',
|
||||
method: 'GET',
|
||||
});
|
||||
getBackendSrv().datasourceRequest({
|
||||
url: this.url + routePath + '/v1/users',
|
||||
method: 'GET',
|
||||
});
|
||||
```
|
||||
|
||||
### Add a dynamic proxy route to your plugin
|
||||
|
@ -13,9 +13,7 @@ By adding a help component to your plugin, you can for example create "cheat she
|
||||
import { QueryEditorHelpProps } from '@grafana/data';
|
||||
|
||||
export default (props: QueryEditorHelpProps) => {
|
||||
return (
|
||||
<h2>My cheat sheet</h2>
|
||||
);
|
||||
return <h2>My cheat sheet</h2>;
|
||||
};
|
||||
```
|
||||
|
||||
@ -62,7 +60,7 @@ By adding a help component to your plugin, you can for example create "cheat she
|
||||
{item.expression ? (
|
||||
<div
|
||||
className="cheat-sheet-item__example"
|
||||
onClick={e => props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)}
|
||||
onClick={(e) => props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)}
|
||||
>
|
||||
<code>{item.expression}</code>
|
||||
</div>
|
||||
|
@ -29,5 +29,6 @@ To enable annotation support for your data source, add the following two lines o
|
||||
**datasource.ts**
|
||||
|
||||
```ts
|
||||
annotations: {};
|
||||
annotations: {
|
||||
}
|
||||
```
|
||||
|
@ -29,9 +29,7 @@ The query editor for Explore is similar to the query editor for the data source
|
||||
export type Props = ExploreQueryFieldProps<DataSource, MyQuery, MyDataSourceOptions>;
|
||||
|
||||
export default (props: Props) => {
|
||||
return (
|
||||
<h2>My query editor</h2>
|
||||
);
|
||||
return <h2>My query editor</h2>;
|
||||
};
|
||||
```
|
||||
|
||||
@ -92,6 +90,7 @@ Explore should by default select a reasonable visualization for your data so use
|
||||
If this does not work for you or you want to show some data in a specific visualization, add a hint to your returned data frame using the `preferredVisualisationType` meta attribute.
|
||||
|
||||
You can construct a data frame with specific metadata:
|
||||
|
||||
```
|
||||
const firstResult = new MutableDataFrame({
|
||||
fields: [...],
|
||||
|
@ -30,10 +30,10 @@ Add `replaceVariables` to the argument list, and pass it a user-defined template
|
||||
|
||||
```ts
|
||||
export const SimplePanel: React.FC<Props> = ({ options, data, width, height, replaceVariables }) => {
|
||||
const query = replaceVariables('Now displaying $service')
|
||||
const query = replaceVariables('Now displaying $service');
|
||||
|
||||
return <div>{ query }</div>
|
||||
}
|
||||
return <div>{query}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Interpolate variables in data source plugins
|
||||
@ -67,7 +67,7 @@ A data source can define the default format option when no format is specified b
|
||||
Let's change the SQL query to use CSV format by default:
|
||||
|
||||
```ts
|
||||
getTemplateSrv().replace('SELECT * FROM services WHERE id IN ($service)', options.scopedVars, "csv");
|
||||
getTemplateSrv().replace('SELECT * FROM services WHERE id IN ($service)', options.scopedVars, 'csv');
|
||||
```
|
||||
|
||||
Now, when users write `$service`, the query looks like this:
|
||||
@ -177,7 +177,13 @@ Let's create a custom query editor to allow the user to edit the query model.
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-10">Query</span>
|
||||
<input name="rawQuery" className="gf-form-input" onBlur={saveQuery} onChange={handleChange} value={state.rawQuery} />
|
||||
<input
|
||||
name="rawQuery"
|
||||
className="gf-form-input"
|
||||
onBlur={saveQuery}
|
||||
onChange={handleChange}
|
||||
value={state.rawQuery}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ Because Grafana maintains the plugin protocol, the plugin protocol attempts to f
|
||||
## Writing plugins without Go
|
||||
|
||||
If you want to write a backend plugin in another language than Go, then it’s possible as long as the language supports [gRPC](https://grpc.io/). However, writing a plugin in Go is recommended and has several advantages that should be carefully taken into account before proceeding:
|
||||
|
||||
- There's an official [SDK]({{< relref "grafana-plugin-sdk-for-go.md" >}}) available.
|
||||
- Single binary as the compiled output.
|
||||
- Building and compiling for multiple platforms is easy.
|
||||
|
@ -45,8 +45,8 @@ Grafana uses [RxJS](https://rxjs.dev/) to continuously send data from a data sou
|
||||
```
|
||||
|
||||
```ts
|
||||
const observables = options.targets.map(target => {
|
||||
return new Observable<DataQueryResponse>(subscriber => {
|
||||
const observables = options.targets.map((target) => {
|
||||
return new Observable<DataQueryResponse>((subscriber) => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
@ -99,38 +99,38 @@ Grafana uses [RxJS](https://rxjs.dev/) to continuously send data from a data sou
|
||||
|
||||
Here's the final `query` method.
|
||||
|
||||
```ts
|
||||
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
|
||||
const streams = options.targets.map(target => {
|
||||
const query = defaults(target, defaultQuery);
|
||||
```ts
|
||||
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
|
||||
const streams = options.targets.map(target => {
|
||||
const query = defaults(target, defaultQuery);
|
||||
|
||||
return new Observable<DataQueryResponse>(subscriber => {
|
||||
const frame = new CircularDataFrame({
|
||||
append: 'tail',
|
||||
capacity: 1000,
|
||||
});
|
||||
return new Observable<DataQueryResponse>(subscriber => {
|
||||
const frame = new CircularDataFrame({
|
||||
append: 'tail',
|
||||
capacity: 1000,
|
||||
});
|
||||
|
||||
frame.refId = query.refId;
|
||||
frame.addField({ name: 'time', type: FieldType.time });
|
||||
frame.addField({ name: 'value', type: FieldType.number });
|
||||
frame.refId = query.refId;
|
||||
frame.addField({ name: 'time', type: FieldType.time });
|
||||
frame.addField({ name: 'value', type: FieldType.number });
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
frame.add({ time: Date.now(), value: Math.random() });
|
||||
const intervalId = setInterval(() => {
|
||||
frame.add({ time: Date.now(), value: Math.random() });
|
||||
|
||||
subscriber.next({
|
||||
data: [frame],
|
||||
key: query.refId,
|
||||
});
|
||||
}, 100);
|
||||
subscriber.next({
|
||||
data: [frame],
|
||||
key: query.refId,
|
||||
});
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return merge(...streams);
|
||||
}
|
||||
```
|
||||
return merge(...streams);
|
||||
}
|
||||
```
|
||||
|
||||
One limitation with this example is that the panel visualization is cleared every time you update the dashboard. If you have access to historical data, you can add, or _backfill_, it to the data frame before the first call to `subscriber.next()`.
|
||||
|
@ -27,14 +27,13 @@ To use a custom panel option editor, use the `addCustomEditor` on the `OptionsUI
|
||||
**module.ts**
|
||||
|
||||
```ts
|
||||
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions(builder => {
|
||||
return builder
|
||||
.addCustomEditor({
|
||||
id: 'label',
|
||||
path: 'label',
|
||||
name: 'Label',
|
||||
editor: SimpleEditor,
|
||||
});
|
||||
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
|
||||
return builder.addCustomEditor({
|
||||
id: 'label',
|
||||
path: 'label',
|
||||
name: 'Label',
|
||||
editor: SimpleEditor,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@ -52,7 +51,7 @@ interface Settings {
|
||||
to: number;
|
||||
}
|
||||
|
||||
export const SimpleEditor: React.FC<StandardEditorProps<number, Settings>> = ({ item, value, onChange }) => {
|
||||
export const SimpleEditor: React.FC<StandardEditorProps<number, Settings>> = ({ item, value, onChange }) => {
|
||||
const options: Array<SelectableValue<number>> = [];
|
||||
|
||||
// Default values
|
||||
@ -66,25 +65,24 @@ export const SimpleEditor: React.FC<StandardEditorProps<number, Settings>> = ({
|
||||
});
|
||||
}
|
||||
|
||||
return <Select options={options} value={value} onChange={selectableValue => onChange(selectableValue.value)} />;
|
||||
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
|
||||
};
|
||||
```
|
||||
|
||||
You can now configure the editor for each option, by configuring the `settings` property in the call to `addCustomEditor`.
|
||||
|
||||
```ts
|
||||
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions(builder => {
|
||||
return builder
|
||||
.addCustomEditor({
|
||||
id: 'index',
|
||||
path: 'index',
|
||||
name: 'Index',
|
||||
editor: SimpleEditor,
|
||||
settings: {
|
||||
from: 1,
|
||||
to: 10,
|
||||
}
|
||||
});
|
||||
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
|
||||
return builder.addCustomEditor({
|
||||
id: 'index',
|
||||
path: 'index',
|
||||
name: 'Index',
|
||||
editor: SimpleEditor,
|
||||
settings: {
|
||||
from: 1,
|
||||
to: 10,
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@ -113,7 +111,7 @@ export const SimpleEditor: React.FC<StandardEditorProps<string>> = ({ item, valu
|
||||
}
|
||||
}
|
||||
|
||||
return <Select options={options} value={value} onChange={selectableValue => onChange(selectableValue.value)} />;
|
||||
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -16,7 +16,7 @@ Allow the user to learn your plugin in small steps. Provide a useful default con
|
||||
For example, by selecting the first field of an expected type, the panel can display a visualization without any user configuration. If a user explicitly selects a field, then use that one. Otherwise, default to the first field of type `string`:
|
||||
|
||||
```ts
|
||||
const numberField = frame.fields.find(field =>
|
||||
const numberField = frame.fields.find((field) =>
|
||||
options.numberFieldName ? field.name === options.numberFieldName : field.type === FieldType.number
|
||||
);
|
||||
```
|
||||
@ -54,10 +54,10 @@ Users have full freedom when they create data source queries for panels. If your
|
||||
|
||||
```ts
|
||||
if (!numberField) {
|
||||
throw new Error('Query result is missing a number field')
|
||||
throw new Error('Query result is missing a number field');
|
||||
}
|
||||
|
||||
if (frame.length === 0) {
|
||||
throw new Error('Query returned an empty result')
|
||||
throw new Error('Query returned an empty result');
|
||||
}
|
||||
```
|
||||
|
@ -45,15 +45,15 @@ The javascript object that communicates with the database and transforms data to
|
||||
The Data source should contain the following functions:
|
||||
|
||||
```javascript
|
||||
query(options) // used by panels to get data
|
||||
testDatasource() // used by data source configuration page to make sure the connection is working
|
||||
annotationQuery(options) // used by dashboards to get annotations
|
||||
metricFindQuery(options) // used by query editor to get metric suggestions.
|
||||
query(options); // used by panels to get data
|
||||
testDatasource(); // used by data source configuration page to make sure the connection is working
|
||||
annotationQuery(options); // used by dashboards to get annotations
|
||||
metricFindQuery(options); // used by query editor to get metric suggestions.
|
||||
```
|
||||
|
||||
### testDatasource
|
||||
|
||||
When a user clicks on the *Save & Test* button when adding a new data source, the details are first saved to the database and then the `testDatasource` function that is defined in your data source plugin will be called. It is recommended that this function makes a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
When a user clicks on the _Save & Test_ button when adding a new data source, the details are first saved to the database and then the `testDatasource` function that is defined in your data source plugin will be called. It is recommended that this function makes a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
|
||||
### Query
|
||||
|
||||
@ -81,15 +81,15 @@ An array of:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"target":"upper_75",
|
||||
"datapoints":[
|
||||
"target": "upper_75",
|
||||
"datapoints": [
|
||||
[622, 1450754160000],
|
||||
[365, 1450754220000]
|
||||
]
|
||||
},
|
||||
{
|
||||
"target":"upper_90",
|
||||
"datapoints":[
|
||||
"target": "upper_90",
|
||||
"datapoints": [
|
||||
[861, 1450754160000],
|
||||
[767, 1450754220000]
|
||||
]
|
||||
@ -118,16 +118,8 @@ An array of:
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
1457425380000,
|
||||
null,
|
||||
null
|
||||
],
|
||||
[
|
||||
1457425370000,
|
||||
1002.76215352,
|
||||
1002.76215352
|
||||
]
|
||||
[1457425380000, null, null],
|
||||
[1457425370000, 1002.76215352, 1002.76215352]
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
@ -141,7 +133,7 @@ Request object passed to datasource.annotationQuery function:
|
||||
```json
|
||||
{
|
||||
"range": { "from": "2016-03-04T04:07:55.144Z", "to": "2016-03-04T07:07:55.144Z" },
|
||||
"rangeRaw": { "from": "now-3h", to: "now" },
|
||||
"rangeRaw": { "from": "now-3h", "to": "now" },
|
||||
"annotation": {
|
||||
"datasource": "generic datasource",
|
||||
"enable": true,
|
||||
@ -160,7 +152,7 @@ Expected result from datasource.annotationQuery:
|
||||
"name": "annotation name", //should match the annotation name in grafana
|
||||
"enabled": true,
|
||||
"datasource": "generic datasource"
|
||||
},
|
||||
},
|
||||
"title": "Cluster outage",
|
||||
"time": 1457075272576,
|
||||
"text": "Joe causes brain split",
|
||||
|
@ -61,13 +61,13 @@ Grafana conventions mean all you need to do is to hook up an Angular template wi
|
||||
|
||||
## Using Events
|
||||
|
||||
To add an editor tab you need to hook into the event model so that the tab is added when the *init-edit-mode* event is triggered. The following code should be added to the constructor of the plugin Ctrl class:
|
||||
To add an editor tab you need to hook into the event model so that the tab is added when the _init-edit-mode_ event is triggered. The following code should be added to the constructor of the plugin Ctrl class:
|
||||
|
||||
```javascript
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
|
||||
```
|
||||
|
||||
Then you need to create a handler function that is bound to the event. In the example above, the handler is called onInitEditMode. The tab is added by calling the controller function, *addEditorTab*. This function has three parameters; the tab name, the path to a html template for the new editor tab and the tab number. It can be a bit tricky to figure out the path, the path name will be based on the id that is specified in the plugin.json file - for example **grafana-clock-panel**. The code below hooks up an Angular template called editor.html that is located in the `src/partials` directory.
|
||||
Then you need to create a handler function that is bound to the event. In the example above, the handler is called onInitEditMode. The tab is added by calling the controller function, _addEditorTab_. This function has three parameters; the tab name, the path to a html template for the new editor tab and the tab number. It can be a bit tricky to figure out the path, the path name will be based on the id that is specified in the plugin.json file - for example **grafana-clock-panel**. The code below hooks up an Angular template called editor.html that is located in the `src/partials` directory.
|
||||
|
||||
```javascript
|
||||
onInitEditMode() {
|
||||
@ -82,31 +82,42 @@ For editor tabs html, it is best to use Grafana css styles rather than custom st
|
||||
Most editor tabs should use the [gf-form css class](https://github.com/grafana/grafana/blob/main/public/sass/components/_gf-form.scss) from Grafana. The example below has one row with a couple of columns and each column is wrapped in a div like this:
|
||||
|
||||
```html
|
||||
<div class="section gf-form-group">
|
||||
```
|
||||
<div class="section gf-form-group"></div>
|
||||
```
|
||||
|
||||
Then each pair, label and field is wrapped in a div with a gf-form class.
|
||||
|
||||
```html
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Font Size</label>
|
||||
<input type="text" class="gf-form-input width-4" ng-model="ctrl.panel.fontSize" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<input
|
||||
type="text"
|
||||
class="gf-form-input width-4"
|
||||
ng-model="ctrl.panel.fontSize"
|
||||
ng-change="ctrl.render()"
|
||||
ng-model-onblur
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
Note that there are some Angular attributes here. *ng-model* will update the panel data. *ng-change* will render the panel when you change the value. This change will occur on the onblur event due to the *ng-model-onblur* attribute. This means you can see the effect of your changes on the panel while editing.
|
||||
Note that there are some Angular attributes here. _ng-model_ will update the panel data. _ng-change_ will render the panel when you change the value. This change will occur on the onblur event due to the _ng-model-onblur_ attribute. This means you can see the effect of your changes on the panel while editing.
|
||||
|
||||
{{< figure class="float-right" src="/assets/img/blog/clock-panel-editor.png" caption="Panel Editor" >}}
|
||||
|
||||
On the editor tab we use a drop down for 12/24 hour clock, an input field for font size and a color picker for the background color.
|
||||
|
||||
The drop down/select has its own *gf-form-select-wrapper* css class and looks like this:
|
||||
The drop down/select has its own _gf-form-select-wrapper_ css class and looks like this:
|
||||
|
||||
```html
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">12 or 24 hour</label>
|
||||
<div class="gf-form-select-wrapper max-width-9">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.clockType" ng-options="t for t in ['12 hour', '24 hour', 'custom']" ng-change="ctrl.render()"></select>
|
||||
<select
|
||||
class="input-small gf-form-input"
|
||||
ng-model="ctrl.panel.clockType"
|
||||
ng-options="t for t in ['12 hour', '24 hour', 'custom']"
|
||||
ng-change="ctrl.render()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
@ -114,11 +125,11 @@ The drop down/select has its own *gf-form-select-wrapper* css class and looks li
|
||||
The color picker (or spectrum picker) is a component that already exists in Grafana. We use it like this for the background color:
|
||||
|
||||
```html
|
||||
<spectrum-picker class="gf-form-input" ng-model="ctrl.panel.bgColor" ng-change="ctrl.render()" ></spectrum-picker>
|
||||
<spectrum-picker class="gf-form-input" ng-model="ctrl.panel.bgColor" ng-change="ctrl.render()"></spectrum-picker>
|
||||
```
|
||||
|
||||
## Editor Tab Finished
|
||||
|
||||
To reiterate, this all ties together quite neatly. We specify properties and panel defaults in the constructor for the panel controller and these can then be changed in the editor. Grafana takes care of saving the changes.
|
||||
|
||||
One thing to be aware of is that panel defaults are used the first time a panel is created to set the initial values of the panel properties. After the panel is saved then the saved value will be used instead. So beware if you update panel defaults they will not automatically update the property in an existing panel. For example, if you set the default font size to 60px first and then in version 2 of the plugin change it to 50px, existing panels will still have 60px and only new panels will get the new 50px value.
|
||||
One thing to be aware of is that panel defaults are used the first time a panel is created to set the initial values of the panel properties. After the panel is saved then the saved value will be used instead. So beware if you update panel defaults they will not automatically update the property in an existing panel. For example, if you set the default font size to 60px first and then in version 2 of the plugin change it to 50px, existing panels will still have 60px and only new panels will get the new 50px value.
|
||||
|
@ -11,7 +11,8 @@ Panels are the main building blocks of dashboards.
|
||||
## Panel development
|
||||
|
||||
### Scrolling
|
||||
The grafana dashboard framework controls the panel height. To enable a scrollbar within the panel the PanelCtrl needs to set the scrollable static variable:
|
||||
|
||||
The grafana dashboard framework controls the panel height. To enable a scrollbar within the panel the PanelCtrl needs to set the scrollable static variable:
|
||||
|
||||
```javascript
|
||||
export class MyPanelCtrl extends PanelCtrl {
|
||||
@ -19,7 +20,7 @@ export class MyPanelCtrl extends PanelCtrl {
|
||||
...
|
||||
```
|
||||
|
||||
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
|
||||
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
|
||||
|
||||
### Examples
|
||||
|
||||
|
@ -48,20 +48,19 @@ A minimal `plugin.json` file:
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "3.x.x",
|
||||
"plugins": [ ]
|
||||
"plugins": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The convention for the plugin id is **[grafana.com username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique. The org **cannot** be `grafana` unless it is a plugin created by the Grafana core team.
|
||||
|
||||
Examples:
|
||||
|
||||
- raintank-worldping-app
|
||||
- ryantxu-ajax-panel
|
||||
- alexanderzobnin-zabbix-app
|
||||
- hawkular-datasource
|
||||
Examples:
|
||||
|
||||
- raintank-worldping-app
|
||||
- ryantxu-ajax-panel
|
||||
- alexanderzobnin-zabbix-app
|
||||
- hawkular-datasource
|
||||
|
||||
- The `type` field should be either `datasource` `app` or `panel`.
|
||||
- The `version` field should be in the form: x.x.x e.g. `1.0.0` or `0.4.1`.
|
||||
@ -118,11 +117,23 @@ Below is a minimal example of an editor row with one form group and two fields,
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label1</label>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.mySelectProperty" ng-options="t for t in ['option1', 'option2', 'option3']" ng-change="ctrl.onSelectChange()"></select>
|
||||
<select
|
||||
class="input-small gf-form-input"
|
||||
ng-model="ctrl.panel.mySelectProperty"
|
||||
ng-options="t for t in ['option1', 'option2', 'option3']"
|
||||
ng-change="ctrl.onSelectChange()"
|
||||
></select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label2</label>
|
||||
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.myProperty" ng-change="ctrl.onFieldChange()" placeholder="suggestion for user" ng-model-onblur />
|
||||
<input
|
||||
type="text"
|
||||
class="input-small gf-form-input width-10"
|
||||
ng-model="ctrl.panel.myProperty"
|
||||
ng-change="ctrl.onFieldChange()"
|
||||
placeholder="suggestion for user"
|
||||
ng-model-onblur
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -132,22 +143,19 @@ Below is a minimal example of an editor row with one form group and two fields,
|
||||
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
|
||||
|
||||
## Data Sources
|
||||
|
||||
For more information about data sources, refer to the [basic guide for data sources](http://docs.grafana.org/plugins/developing/datasources/).
|
||||
|
||||
### Configuration Page Guidelines
|
||||
|
||||
- It should be as easy as possible for a user to configure a URL. If the data source is using the `datasource-http-settings` component, it should use the `suggest-url` attribute to suggest the default URL or a URL that is similar to what it should be (especially important if the URL refers to a REST endpoint that is not common knowledge for most users e.g. `https://yourserver:4000/api/custom-endpoint`).
|
||||
|
||||
```html
|
||||
<datasource-http-settings
|
||||
current="ctrl.current"
|
||||
suggest-url="http://localhost:8080">
|
||||
</datasource-http-settings>
|
||||
```
|
||||
```html
|
||||
<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:8080"> </datasource-http-settings>
|
||||
```
|
||||
|
||||
- The `testDatasource` function should make a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
|
||||
|
||||
#### Password Security
|
||||
|
||||
If possible, any passwords or secrets should be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted.
|
||||
@ -156,9 +164,8 @@ Read more here about how [authentication for data sources]({{< relref "../add-au
|
||||
|
||||
If using the proxy feature, the Configuration page should use the `secureJsonData` blob like this:
|
||||
|
||||
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
|
||||
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
|
||||
|
||||
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
|
||||
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
|
||||
|
||||
### Query Editor
|
||||
|
||||
|
@ -14,7 +14,7 @@ The plugin.json file is required for all plugins. When Grafana starts, it scans
|
||||
## Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|-----------------|-------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| --------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `dependencies` | [object](#dependencies) | **Yes** | Dependencies needed by the plugin. |
|
||||
| `id` | string | **Yes** | Unique name of the plugin. If the plugin is published on grafana.com, then the plugin id has to follow the naming conventions. |
|
||||
| `info` | [object](#info) | **Yes** | Metadata for the plugin. Some fields are used on the plugins page in Grafana and others on grafana.com if the plugin is published. |
|
||||
@ -33,7 +33,7 @@ The plugin.json file is required for all plugins. When Grafana starts, it scans
|
||||
| `metrics` | boolean | No | For data source plugins. If the plugin supports metric queries. Used in the Explore feature. |
|
||||
| `preload` | boolean | No | Initialize plugin on startup. By default, the plugin initializes on first use. |
|
||||
| `queryOptions` | [object](#queryoptions) | No | For data source plugins. There is a query options section in the plugin's query editor and these options can be turned on if needed. |
|
||||
| `routes` | [object](#routes)[] | No | For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Add authentication for data source plugins]({{< relref "add-authentication-for-data-source-plugins.md">}}). |
|
||||
| `routes` | [object](#routes)[] | No | For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Add authentication for data source plugins]({{< relref "add-authentication-for-data-source-plugins.md">}}). |
|
||||
| `skipDataQuery` | boolean | No | For panel plugins. Hides the query editor. |
|
||||
| `state` | string | No | Marks a plugin as a pre-release. Possible values are: `alpha`, `beta`. |
|
||||
| `streaming` | boolean | No | For data source plugins. If the plugin supports streaming. |
|
||||
@ -47,7 +47,7 @@ Dependencies needed by the plugin.
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|---------------------|----------------------|----------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `grafanaDependency` | string | **Yes** | Required Grafana version for this plugin. Validated using https://github.com/npm/node-semver. |
|
||||
| `grafanaVersion` | string | No | (Deprecated) Required Grafana version for this plugin, e.g. `6.x.x 7.x.x` to denote plugin requires Grafana v6.x.x or v7.x.x. |
|
||||
| `plugins` | [object](#plugins)[] | No | An array of required plugins on which this plugin depends. |
|
||||
@ -59,7 +59,7 @@ Plugin dependency. Used to display information about plugin dependencies in the
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|-----------|--------|----------|----------------------------------------------------|
|
||||
| --------- | ------ | -------- | -------------------------------------------------- |
|
||||
| `id` | string | **Yes** | |
|
||||
| `name` | string | **Yes** | |
|
||||
| `type` | string | **Yes** | Possible values are: `app`, `datasource`, `panel`. |
|
||||
@ -70,7 +70,7 @@ Plugin dependency. Used to display information about plugin dependencies in the
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|--------------|---------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `addToNav` | boolean | No | Add the include to the side menu. |
|
||||
| `component` | string | No | (Legacy) The Angular component to use for a page. |
|
||||
| `defaultNav` | boolean | No | Page or dashboard when user clicks the icon in the side menu. |
|
||||
@ -87,7 +87,7 @@ Metadata for the plugin. Some fields are used on the plugins page in Grafana and
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|---------------|--------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `keywords` | string[] | **Yes** | Array of plugin keywords. Used for search on grafana.com. |
|
||||
| `logos` | [object](#logos) | **Yes** | SVG images that are used as plugin icons. |
|
||||
| `updated` | string | **Yes** | Date when this plugin was built. |
|
||||
@ -105,7 +105,7 @@ Information about the plugin author.
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|--------|----------|---------------------------|
|
||||
| -------- | ------ | -------- | ------------------------- |
|
||||
| `email` | string | No | Author's name. |
|
||||
| `name` | string | No | Author's name. |
|
||||
| `url` | string | No | Link to author's website. |
|
||||
@ -117,7 +117,7 @@ Build information
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|--------|----------|------------------------------------------------------|
|
||||
| -------- | ------ | -------- | ---------------------------------------------------- |
|
||||
| `branch` | string | No | Git branch the plugin was built from. |
|
||||
| `hash` | string | No | Git hash of the commit the plugin was built from |
|
||||
| `number` | number | No | |
|
||||
@ -130,7 +130,7 @@ Build information
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|--------|----------|-------------|
|
||||
| -------- | ------ | -------- | ----------- |
|
||||
| `name` | string | No | |
|
||||
| `url` | string | No | |
|
||||
|
||||
@ -141,7 +141,7 @@ SVG images that are used as plugin icons.
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|--------|----------|------------------------------------------------------------------------------------------------------------------------------|
|
||||
| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `large` | string | **Yes** | Link to the "large" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
|
||||
| `small` | string | **Yes** | Link to the "small" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
|
||||
|
||||
@ -150,7 +150,7 @@ SVG images that are used as plugin icons.
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|--------|----------|-------------|
|
||||
| -------- | ------ | -------- | ----------- |
|
||||
| `name` | string | No | |
|
||||
| `path` | string | No | |
|
||||
|
||||
@ -161,7 +161,7 @@ For data source plugins. There is a query options section in the plugin's query
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|-----------------|---------|----------|----------------------------------------------------------------------------------------------------------------------------|
|
||||
| --------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `cacheTimeout` | boolean | No | For data source plugins. If the `cache timeout` option should be shown in the query options section in the query editor. |
|
||||
| `maxDataPoints` | boolean | No | For data source plugins. If the `max data points` option should be shown in the query options section in the query editor. |
|
||||
| `minInterval` | boolean | No | For data source plugins. If the `min interval` option should be shown in the query options section in the query editor. |
|
||||
@ -173,7 +173,7 @@ For data source plugins. Proxy routes used for plugin authentication and adding
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------------|-------------------------|----------|---------------------------------------------------------------------------------------------------------|
|
||||
| -------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
|
||||
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
|
||||
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
|
||||
@ -189,7 +189,7 @@ For data source plugins. Proxy routes used for plugin authentication and adding
|
||||
For data source plugins. Route headers set the body content and length to the proxied request.
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| -------- | ---- | -------- | ----------- |
|
||||
|
||||
### jwtTokenAuth
|
||||
|
||||
@ -198,7 +198,7 @@ For data source plugins. Token authentication section used with an JWT OAuth API
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|-------------------|----------|------------------------------------------------------|
|
||||
| -------- | ----------------- | -------- | ---------------------------------------------------- |
|
||||
| `params` | [object](#params) | No | Parameters for the JWT token authentication request. |
|
||||
| `scopes` | string | No | |
|
||||
| `url` | string | No | URL to fetch the JWT token. |
|
||||
@ -210,7 +210,7 @@ Parameters for the JWT token authentication request.
|
||||
##### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------------|----------|----------|-------------|
|
||||
| -------------- | -------- | -------- | ----------- |
|
||||
| `client_email` | string | No | |
|
||||
| `private_key` | string | No | |
|
||||
| `scopes` | string[] | No | |
|
||||
@ -223,7 +223,7 @@ For data source plugins. Token authentication section used with an OAuth API.
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|-------------------|----------|--------------------------------------------------|
|
||||
| -------- | ----------------- | -------- | ------------------------------------------------ |
|
||||
| `params` | [object](#params) | No | Parameters for the token authentication request. |
|
||||
| `scopes` | string | No | |
|
||||
| `url` | string | No | URL to fetch the authentication token. |
|
||||
@ -235,10 +235,8 @@ Parameters for the token authentication request.
|
||||
##### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|-----------------|--------|----------|-------------------------------------------------------------------------------------------|
|
||||
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
|
||||
| `client_id` | string | No | OAuth client ID |
|
||||
| `client_secret` | string | No | OAuth client secret. Usually populated by decrypting the secret from the SecureJson blob. |
|
||||
| `grant_type` | string | No | OAuth grant type |
|
||||
| `resource` | string | No | OAuth resource |
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ To sign a plugin, you need to decide the _signature level_ you want to sign it u
|
||||
You can sign your plugin under three different _signature levels_.
|
||||
|
||||
| **Plugin Level** | **Paid Subscription Required?** | **Description** |
|
||||
|------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ---------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Private | No;<br>Free of charge | <p>You can create and sign a Private Plugin for any technology at no charge.</p><p>Private Plugins are for use on your own Grafana. They may not be distributed to the Grafana community, and are not published in the Grafana catalog.</p> |
|
||||
| Community | No;<br>Free of charge | <p>You can create, sign and distribute plugins at no charge, provided that all dependent technologies are open source and not for profit.</p><p>Community Plugins are published in the official Grafana catalog, and are available to the Grafana community.</p> |
|
||||
| Commercial | Yes;<br>Commercial Plugin Subscription required | <p>You can create, sign and distribute plugins with dependent technologies that are closed source or commercially backed, by entering into a Commercial Plugin Subscription with Grafana Labs.</p><p>Commercial Plugins are published on the official Grafana catalog, and are available to the Grafana community.</p> |
|
||||
@ -73,7 +73,7 @@ For instructions on how to sign a plugin under the Private signature level, refe
|
||||
For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, _MANIFEST.txt_. The signed manifest file contains two sections:
|
||||
|
||||
- **Signed message -** The signed message contains plugin metadata and plugin files with their respective checksums (SHA256).
|
||||
- **Digital signature -** The digital signature is created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature have been encrypted using expected private key.
|
||||
- **Digital signature -** The digital signature is created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature have been encrypted using expected private key.
|
||||
|
||||
**Example manifest file:**
|
||||
|
||||
|
@ -23,11 +23,11 @@ const numberValues = [12.3, 28.6];
|
||||
|
||||
// Create data frame from values.
|
||||
const frame = toDataFrame({
|
||||
name: "http_requests_total",
|
||||
fields: [
|
||||
{ name: "Time", type: FieldType.time, values: timeValues },
|
||||
{ name: "Value", type: FieldType.number, values: numberValues }
|
||||
]
|
||||
name: 'http_requests_total',
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: timeValues },
|
||||
{ name: 'Value', type: FieldType.number, values: numberValues },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@ -37,12 +37,12 @@ As you can see from the example, creating data frames like this requires that yo
|
||||
|
||||
```ts
|
||||
const series = [
|
||||
{ Time: 1599471973065, Value: 12.3 },
|
||||
{ Time: 1599471975729, Value: 28.6 }
|
||||
{ Time: 1599471973065, Value: 12.3 },
|
||||
{ Time: 1599471975729, Value: 28.6 },
|
||||
];
|
||||
|
||||
const frame = toDataFrame(series);
|
||||
frame.name = 'http_requests_total'
|
||||
frame.name = 'http_requests_total';
|
||||
```
|
||||
|
||||
## Read values from a data frame
|
||||
@ -54,22 +54,22 @@ const SimplePanel: React.FC<Props> = ({ data }) => {
|
||||
const frame = data.series[0];
|
||||
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Before you start reading the data, think about what data you expect. For example, to visualize a time series we'd need at least one time field, and one number field.
|
||||
|
||||
```ts
|
||||
const timeField = frame.fields.find(field => field.type === FieldType.time);
|
||||
const valueField = frame.fields.find(field => field.type === FieldType.number);
|
||||
const timeField = frame.fields.find((field) => field.type === FieldType.time);
|
||||
const valueField = frame.fields.find((field) => field.type === FieldType.number);
|
||||
```
|
||||
|
||||
Other types of visualizations might need multiple dimensions. For example, a bubble chart that uses three numeric fields: the X-axis, Y-axis, and one for the radius of each bubble. In this case, instead of hard coding the field names, we recommend that you let the user choose the field to use for each dimension.
|
||||
|
||||
```ts
|
||||
const x = frame.fields.find(field => field.name === xField);
|
||||
const y = frame.fields.find(field => field.name === yField);
|
||||
const size = frame.fields.find(field => field.name === sizeField);
|
||||
const x = frame.fields.find((field) => field.name === xField);
|
||||
const y = frame.fields.find((field) => field.name === yField);
|
||||
const size = frame.fields.find((field) => field.name === sizeField);
|
||||
|
||||
for (let i = 0; i < frame.length; i++) {
|
||||
const row = [x?.values.get(i), y?.values.get(i), size?.values.get(i)];
|
||||
@ -83,9 +83,9 @@ Alternatively, you can use the [DataFrameView]({{< relref "../../packages_api/da
|
||||
```ts
|
||||
const view = new DataFrameView(frame);
|
||||
|
||||
view.forEach(row => {
|
||||
view.forEach((row) => {
|
||||
console.log(row[options.xField], row[options.yField], row[options.sizeField]);
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Display values from a data frame
|
||||
@ -95,12 +95,12 @@ Field options let the user control how Grafana displays the data in a data frame
|
||||
To apply the field options to a value, use the `display` method on the corresponding field. The result contains information such as the color and suffix to use when display the value.
|
||||
|
||||
```ts
|
||||
const valueField = frame.fields.find(field => field.type === FieldType.number);
|
||||
const valueField = frame.fields.find((field) => field.type === FieldType.number);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{valueField
|
||||
? valueField.values.toArray().map(value => {
|
||||
? valueField.values.toArray().map((value) => {
|
||||
const displayValue = valueField.display!(value);
|
||||
return (
|
||||
<p style={{ color: displayValue.color }}>
|
||||
@ -116,7 +116,6 @@ return (
|
||||
To apply field options to the name of a field, use [getFieldDisplayName]({{< relref "../../packages_api/data/getfielddisplayname.md" >}}).
|
||||
|
||||
```ts
|
||||
const valueField = frame.fields.find(field => field.type === FieldType.number);
|
||||
const valueField = frame.fields.find((field) => field.type === FieldType.number);
|
||||
const valueFieldName = getFieldDisplayName(valueField, frame);
|
||||
```
|
||||
|
||||
|
Reference in New Issue
Block a user