├── .gitignore ├── dist ├── css │ └── query-editor.css ├── img │ ├── sample_query.png │ ├── table_panel.png │ ├── sample_dashboard.png │ ├── sample_datasource.png │ ├── sample_template.png │ └── MongoDB_Gray_Logo_FullColor_RGB-01.jpg ├── partials │ ├── query.options.html │ ├── annotations.editor.html │ ├── config.html │ └── query.editor.html ├── server │ ├── config │ │ └── default.json │ ├── mongodb-grafana-proxy.plist │ └── mongodb-proxy.js ├── plugin.json ├── test │ ├── spec │ │ ├── test-main.js │ │ ├── test-main.js.map │ │ ├── datasource_spec.js │ │ └── datasource_spec.js.map │ ├── module.js.map │ ├── module.js │ ├── query_ctrl.js.map │ ├── query_ctrl.js │ ├── datasource.js │ └── datasource.js.map ├── module.js.map ├── query_ctrl.js.map ├── module.js ├── query_ctrl.js ├── README.md ├── datasource.js └── datasource.js.map ├── src ├── css │ └── query-editor.css ├── img │ ├── sample_query.png │ ├── table_panel.png │ ├── sample_template.png │ ├── sample_dashboard.png │ ├── sample_datasource.png │ └── MongoDB_Gray_Logo_FullColor_RGB-01.jpg ├── partials │ ├── query.options.html │ ├── annotations.editor.html │ ├── config.html │ └── query.editor.html ├── module.js ├── query_ctrl.js ├── plugin.json └── datasource.js ├── .vscode ├── settings.json └── launch.json ├── debugging ├── start_grafana.sh └── grafana.ini ├── server ├── config │ └── default.json ├── mongodb-grafana-proxy.plist └── mongodb-proxy.js ├── spec ├── test-main.js └── datasource_spec.js ├── LICENSE ├── package.json ├── Gruntfile.js ├── examples ├── Sensor Value Counts - Atlas.json ├── RPI Mongo Bucket - Atlas Temp.json └── RPI Mongo Bucket - Atlas CS.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .gradle/ 3 | -------------------------------------------------------------------------------- /dist/css/query-editor.css: -------------------------------------------------------------------------------- 1 | .generic-datasource-query-row .query-keyword { 2 | width: 75px; 3 | } -------------------------------------------------------------------------------- /src/css/query-editor.css: -------------------------------------------------------------------------------- 1 | .generic-datasource-query-row .query-keyword { 2 | width: 75px; 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "grafana", 4 | "timeserie" 5 | ] 6 | } -------------------------------------------------------------------------------- /dist/img/sample_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/sample_query.png -------------------------------------------------------------------------------- /dist/img/table_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/table_panel.png -------------------------------------------------------------------------------- /src/img/sample_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/sample_query.png -------------------------------------------------------------------------------- /src/img/table_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/table_panel.png -------------------------------------------------------------------------------- /src/img/sample_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/sample_template.png -------------------------------------------------------------------------------- /dist/img/sample_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/sample_dashboard.png -------------------------------------------------------------------------------- /dist/img/sample_datasource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/sample_datasource.png -------------------------------------------------------------------------------- /dist/img/sample_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/sample_template.png -------------------------------------------------------------------------------- /src/img/sample_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/sample_dashboard.png -------------------------------------------------------------------------------- /src/img/sample_datasource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/sample_datasource.png -------------------------------------------------------------------------------- /src/partials/query.options.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /dist/partials/query.options.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /dist/img/MongoDB_Gray_Logo_FullColor_RGB-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/dist/img/MongoDB_Gray_Logo_FullColor_RGB-01.jpg -------------------------------------------------------------------------------- /src/img/MongoDB_Gray_Logo_FullColor_RGB-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesOsgood/mongodb-grafana/HEAD/src/img/MongoDB_Gray_Logo_FullColor_RGB-01.jpg -------------------------------------------------------------------------------- /debugging/start_grafana.sh: -------------------------------------------------------------------------------- 1 | /usr/local/opt/grafana/bin/grafana-server --config /Users/james/code/github/grafana/plugins/mongodb-grafana/debugging/grafana.ini --homepath /usr/local/opt/grafana/share/grafana -------------------------------------------------------------------------------- /server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": 3 | { 4 | "port": 3333, 5 | "logRequests": false, 6 | "logQueries": false, 7 | "logTimings": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dist/server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": 3 | { 4 | "port": 3333, 5 | "logRequests": false, 6 | "logQueries": false, 7 | "logTimings": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dist/partials/annotations.editor.html: -------------------------------------------------------------------------------- 1 | 2 |
32 |
33 | Then save the data source
34 |
35 | #### Example 1 - Simple aggregate to rename fields
36 |
37 | Import the dashboard in `examples\RPI MongoDB - Atlas.json`
38 |
39 | This should show a graph of light sensor values from a Raspberry PI with an [EnviroPHAT](https://thepihut.com/products/enviro-phat) board feeding readings every minute into a MongoDB Atlas database.
40 |
41 |
42 |
43 | Clicking on the title of the graph allows you to see the aggregation query being run against the 'RPI Atlas' data source
44 |
45 |
46 |
47 | The query here is
48 |
49 | ```javascript
50 | db.sensor_value.aggregate ( [
51 | { "$match" : { "sensor_type" : "$sensor", "host_name" : "$host", "ts" : { "$gte" : "$from", "$lte" : "$to" } } },
52 | {"$sort" : {"ts" : 1}},
53 | {"$project" : { "name" : "value", "value" : "$sensor_value", "ts" : "$ts", "_id" : 0} } ])
54 | ```
55 |
56 | The API is expecting back documents with the following fields
57 |
58 | * `name` - Name of the series ( will be displayed on the graph)
59 | * `value` - The float value of the point
60 | * `ts` - The time of the point as a BSON date
61 |
62 | These documents are then converted into the [Grafana API](http://docs.grafana.org/plugins/developing/datasources/)
63 |
64 | `$from` and `$to` are expanded by the plugin as BSON dates based on the range settings on the UI.
65 |
66 | `$sensor` and `$host` are template variables that are filled in by Grafana based on the drop down. The sample template queries are shown below. They expect documents to be returned with a single `_id` field.
67 |
68 |
69 |
70 |
71 | #### Example 2 - Using $bucketAuto to push data point aggregation to the server
72 |
73 | Grafana tells the backend server the date range along with the size of the buckets that should be used to calculate points. Therefore it's possible to use the MongoDB aggregation operator [$bucketAuto](https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/) to automatically bucket the data points into display points. To support this the backend provides the `$dateBucketCount` macro so that queries such as the one below can be written
74 |
75 | ```javascript
76 | db.sensor_value.aggregate( [
77 | { "$match" : { "sensor_type" : "$sensor", "host_name" : "$host" , "ts" : { "$gte" : "$from", "$lt" : "$to" }}},
78 | { "$bucketAuto" : { "groupBy" : "$ts",
79 | "buckets" : "$dateBucketCount",
80 | "output" : { "maxValue" : { "$max" : "$sensor_value" } } } },
81 | { "$project" : { "name" : "value", "value" : "$maxValue", "ts" : "$_id.min", "_id" : 0 } } ] )
82 | ```
83 | Note that ```_id``` field of the bucketAuto output contains the start and end of the bucket so we can use that as the ```ts``` value
84 |
85 | The dashboard in `examples\RPI MongoDB Bucket - Atlas.json` shows this.
86 |
87 | #### Example 3 - Using a Tabel Panel
88 |
89 |
90 |
91 | Table panels are now supported with queries of the form
92 |
93 | ```javascript
94 | db.sensor_value.aggregate(
95 | [
96 | { "$match" : { "ts" : { "$gte" : "$from", "$lt" : "$to" }}},
97 | { "$group": { "_id": { "sensor_name" : "$sensor_name", "sensor_type" : "$sensor_type" }, "cnt" : { "$sum" : 1 }, "ts" : { "$max" : "$ts" } } },
98 | { "$project": { "name" : { "$concat" : ["$_id.sensor_name",":","$_id.sensor_type" ]}, "value" : "$cnt", "ts" : 1, "_id" : 0} }
99 | ])
100 | ```
101 |
102 | The dashboard in `examples\Sensor Values Count - Atlas.json` shows this.
103 |
104 | ## Running the proxy as a service on a Mac
105 |
106 | * Install [forever-mac](https://www.npmjs.com/package/forever-mac)
107 | * Copy server/mongodb-grafana-proxy.plist to ~/Library/LaunchAgents
108 | * run `launchctl load mongodb-grafana-proxy` from ~/Library/LaunchAgents
109 |
110 | This launch ctrl plist runs the node script via forever. To check it's running, use `forever list`. Logs go into /usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server
111 |
112 | ## Development
113 |
114 | To run grafana against a dev version of the plugin on a mac using grafana installed via Homebrew
115 |
116 | * Stop the grafana service `brew services stop grafana`
117 | * Open a command prompt in /debugging
118 | * Run ./start_grafana.sh
119 | * Alter code
120 | * npm run build to build the UI
121 | * Developer tools -> empty cache and hard reload
122 |
123 | Note
124 |
125 | * Homebrew grafana versions in /usr/local/Cellar
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MongoDB datasource for Grafana
2 |
3 | ## Features
4 | Allows MongoDB to be used as a data source for Grafana by providing a proxy to convert the Grafana Data source [API](http://docs.grafana.org/plugins/developing/datasources/) into MongoDB aggregation queries
5 |
6 | ## Requirements
7 |
8 | * **Grafana** > 3.x.x
9 | * **MongoDB** > 3.4.x
10 |
11 | ## Installation
12 |
13 | ### Install the Grafana plugin components
14 |
15 | * Copy the whole mongodb-grafana dir into the Grafana plugins dir ( /usr/local/var/lib/grafana/plugins )
16 | * Restart the Grafana server. If installed via Homebrew, this will be `brew services restart grafana`
17 |
18 | ### Install and Start the MongoDB proxy server
19 |
20 | * Open a command prompt in the mongodb-grafana directory
21 | * Run `npm install` to install the node.js dependencies
22 | * Run `npm run server` to start the REST API proxy to MongoDB. By default, the server listens on http://localhost:3333
23 |
24 | ## Examples
25 |
26 | Create a new data source of type MongoDB as shown below. The MongoDB details are :
27 |
28 | * **MongoDB URL** - `mongodb://rpiread:rpiread@rpi-sensor-data-shard-00-00-ifxxs.mongodb.net:27017,rpi-sensor-data-shard-00-01-ifxxs.mongodb.net:27017,rpi-sensor-data-shard-00-02-ifxxs.mongodb.net:27017/test?ssl=true&replicaSet=rpi-sensor-data-shard-0&authSource=admin`
29 | * **MongoDB Database** - `rpi`
30 |
31 |
32 |
33 | Then save the data source
34 |
35 | #### Example 1 - Simple aggregate to rename fields
36 |
37 | Import the dashboard in `examples\RPI MongoDB - Atlas.json`
38 |
39 | This should show a graph of light sensor values from a Raspberry PI with an [EnviroPHAT](https://thepihut.com/products/enviro-phat) board feeding readings every minute into a MongoDB Atlas database.
40 |
41 |
42 |
43 | Clicking on the title of the graph allows you to see the aggregation query being run against the 'RPI Atlas' data source
44 |
45 |
46 |
47 | The query here is
48 |
49 | ```javascript
50 | db.sensor_value.aggregate ( [
51 | { "$match" : { "sensor_type" : "$sensor", "host_name" : "$host", "ts" : { "$gte" : "$from", "$lte" : "$to" } } },
52 | {"$sort" : {"ts" : 1}},
53 | {"$project" : { "name" : "value", "value" : "$sensor_value", "ts" : "$ts", "_id" : 0} } ])
54 | ```
55 |
56 | The API is expecting back documents with the following fields
57 |
58 | * `name` - Name of the series ( will be displayed on the graph)
59 | * `value` - The float value of the point
60 | * `ts` - The time of the point as a BSON date
61 |
62 | These documents are then converted into the [Grafana API](http://docs.grafana.org/plugins/developing/datasources/)
63 |
64 | `$from` and `$to` are expanded by the plugin as BSON dates based on the range settings on the UI.
65 |
66 | ## Template Variables
67 |
68 | `$sensor` and `$host` are template variables that are filled in by Grafana based on the drop down. The sample template queries are shown below. They expect documents to be returned with a single `_id` field.
69 |
70 |
71 |
72 |
73 | #### Example 2 - Using $bucketAuto to push data point aggregation to the server
74 |
75 | Grafana tells the backend server the date range along with the size of the buckets that should be used to calculate points. Therefore it's possible to use the MongoDB aggregation operator [$bucketAuto](https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/) to automatically bucket the data points into display points. To support this the backend provides the `$dateBucketCount` macro so that queries such as the one below can be written
76 |
77 | ```javascript
78 | db.sensor_value.aggregate( [
79 | { "$match" : { "sensor_type" : "$sensor", "host_name" : "$host" , "ts" : { "$gte" : "$from", "$lt" : "$to" }}},
80 | { "$bucketAuto" : { "groupBy" : "$ts",
81 | "buckets" : "$dateBucketCount",
82 | "output" : { "maxValue" : { "$max" : "$sensor_value" } } } },
83 | { "$project" : { "name" : "value", "value" : "$maxValue", "ts" : "$_id.min", "_id" : 0 } } ] )
84 | ```
85 | Note that ```_id``` field of the bucketAuto output contains the start and end of the bucket so we can use that as the ```ts``` value
86 |
87 | The dashboard in `examples\RPI MongoDB Bucket - Atlas.json` shows this.
88 |
89 | #### Example 3 - Using a Tabel Panel
90 |
91 |
92 |
93 | Table panels are now supported with queries of the form
94 |
95 | ```javascript
96 | db.sensor_value.aggregate(
97 | [
98 | { "$match" : { "ts" : { "$gte" : "$from", "$lt" : "$to" }}},
99 | { "$group": { "_id": { "sensor_name" : "$sensor_name", "sensor_type" : "$sensor_type" }, "cnt" : { "$sum" : 1 }, "ts" : { "$max" : "$ts" } } },
100 | { "$project": { "name" : { "$concat" : ["$_id.sensor_name",":","$_id.sensor_type" ]}, "value" : "$cnt", "ts" : 1, "_id" : 0} }
101 | ])
102 | ```
103 |
104 | The dashboard in `examples\Sensor Values Count - Atlas.json` shows this.
105 |
106 | ## Running the proxy as a service on a Mac
107 |
108 | * Install [forever-mac](https://www.npmjs.com/package/forever-mac)
109 | * Copy server/mongodb-grafana-proxy.plist to ~/Library/LaunchAgents
110 | * run `launchctl load mongodb-grafana-proxy` from ~/Library/LaunchAgents
111 |
112 | This launch ctrl plist runs the node script via forever. To check it's running, use `forever list`. Logs go into /usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server
113 |
114 | ## Development
115 |
116 | To run grafana against a dev version of the plugin on a mac using grafana installed via Homebrew
117 |
118 | * Stop the grafana service `brew services stop grafana`
119 | * Open a command prompt in /debugging
120 | * Run ./start_grafana.sh
121 | * Alter code
122 | * npm run build to build the UI
123 | * Developer tools -> empty cache and hard reload
124 |
125 | Note
126 |
127 | * Homebrew grafana versions in /usr/local/Cellar
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/dist/datasource.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | System.register(['lodash'], function (_export, _context) {
4 | "use strict";
5 |
6 | var _, _createClass, GenericDatasource;
7 |
8 | function _classCallCheck(instance, Constructor) {
9 | if (!(instance instanceof Constructor)) {
10 | throw new TypeError("Cannot call a class as a function");
11 | }
12 | }
13 |
14 | return {
15 | setters: [function (_lodash) {
16 | _ = _lodash.default;
17 | }],
18 | execute: function () {
19 | _createClass = function () {
20 | function defineProperties(target, props) {
21 | for (var i = 0; i < props.length; i++) {
22 | var descriptor = props[i];
23 | descriptor.enumerable = descriptor.enumerable || false;
24 | descriptor.configurable = true;
25 | if ("value" in descriptor) descriptor.writable = true;
26 | Object.defineProperty(target, descriptor.key, descriptor);
27 | }
28 | }
29 |
30 | return function (Constructor, protoProps, staticProps) {
31 | if (protoProps) defineProperties(Constructor.prototype, protoProps);
32 | if (staticProps) defineProperties(Constructor, staticProps);
33 | return Constructor;
34 | };
35 | }();
36 |
37 | _export('GenericDatasource', GenericDatasource = function () {
38 | function GenericDatasource(instanceSettings, $q, backendSrv, templateSrv) {
39 | _classCallCheck(this, GenericDatasource);
40 |
41 | this.type = instanceSettings.type;
42 | this.url = instanceSettings.url;
43 | this.name = instanceSettings.name;
44 | this.db = { 'url': instanceSettings.jsonData.mongodb_url, 'db': instanceSettings.jsonData.mongodb_db };
45 | this.q = $q;
46 | this.backendSrv = backendSrv;
47 | this.templateSrv = templateSrv;
48 | this.withCredentials = instanceSettings.withCredentials;
49 | this.headers = { 'Content-Type': 'application/json' };
50 | if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) {
51 | this.headers['Authorization'] = instanceSettings.basicAuth;
52 | }
53 | }
54 |
55 | _createClass(GenericDatasource, [{
56 | key: 'query',
57 | value: function query(options) {
58 | var query = this.buildQueryParameters(options);
59 | query.targets = query.targets.filter(function (t) {
60 | return !t.hide;
61 | });
62 | query.db = this.db;
63 |
64 | if (query.targets.length <= 0) {
65 | return this.q.when({ data: [] });
66 | }
67 |
68 | return this.doRequest({
69 | url: this.url + '/query',
70 | data: query,
71 | method: 'POST'
72 | });
73 | }
74 | }, {
75 | key: 'testDatasource',
76 | value: function testDatasource() {
77 | return this.doRequest({
78 | url: this.url + '/',
79 | data: { db: this.db },
80 | method: 'POST'
81 | }).then(function (response) {
82 | if (response.status === 200) {
83 | return { status: response.data.status, message: response.data.message, title: response.data.display_status };
84 | }
85 | });
86 | }
87 | }, {
88 | key: 'annotationQuery',
89 | value: function annotationQuery(options) {
90 | var query = this.templateSrv.replace(options.annotation.query, {}, 'glob');
91 | var annotationQuery = {
92 | range: options.range,
93 | annotation: {
94 | name: options.annotation.name,
95 | datasource: options.annotation.datasource,
96 | enable: options.annotation.enable,
97 | iconColor: options.annotation.iconColor,
98 | query: query
99 | },
100 | rangeRaw: options.rangeRaw
101 | };
102 |
103 | return this.doRequest({
104 | url: this.url + '/annotations',
105 | method: 'POST',
106 | data: annotationQuery
107 | }).then(function (result) {
108 | response.data.$$status = result.status;
109 | response.data.$$config = result.config;
110 | return result.data;
111 | });
112 | }
113 | }, {
114 | key: 'metricFindQuery',
115 | value: function metricFindQuery(query) {
116 | var interpolated = {
117 | target: this.templateSrv.replace(query, null, '')
118 | };
119 | interpolated.db = this.db;
120 |
121 | return this.doRequest({
122 | url: this.url + '/search',
123 | data: interpolated,
124 | method: 'POST'
125 | }).then(this.mapToTextValue);
126 | }
127 | }, {
128 | key: 'mapToTextValue',
129 | value: function mapToTextValue(result) {
130 | return _.map(result.data, function (d, i) {
131 | if (d && d.text && d.value) {
132 | return { text: d.text, value: d.value };
133 | } else if (_.isObject(d)) {
134 | return { text: d, value: i };
135 | }
136 | return { text: d, value: d };
137 | });
138 | }
139 | }, {
140 | key: 'doRequest',
141 | value: function doRequest(options) {
142 | options.withCredentials = this.withCredentials;
143 | options.headers = this.headers;
144 |
145 | return this.backendSrv.datasourceRequest(options);
146 | }
147 | }, {
148 | key: 'buildQueryParameters',
149 | value: function buildQueryParameters(options) {
150 | var _this = this;
151 |
152 | //remove place holder targets
153 | options.targets = _.filter(options.targets, function (target) {
154 | return target.target !== 'select metric';
155 | });
156 |
157 | var targets = _.map(options.targets, function (target) {
158 | return {
159 | target: _this.templateSrv.replace(target.target, options.scopedVars, ''),
160 | refId: target.refId,
161 | hide: target.hide,
162 | type: target.type || 'timeserie'
163 | };
164 | });
165 |
166 | options.targets = targets;
167 |
168 | return options;
169 | }
170 | }]);
171 |
172 | return GenericDatasource;
173 | }());
174 |
175 | _export('GenericDatasource', GenericDatasource);
176 | }
177 | };
178 | });
179 | //# sourceMappingURL=datasource.js.map
180 |
--------------------------------------------------------------------------------
/examples/RPI Mongo Bucket - Atlas Temp.json:
--------------------------------------------------------------------------------
1 | {
2 | "__inputs": [
3 | {
4 | "name": "DS_RPI_- ATLAS",
5 | "label": "RPI - Atlas",
6 | "description": "",
7 | "type": "datasource",
8 | "pluginId": "grafana-mongodb-datasource",
9 | "pluginName": "MongoDB"
10 | }
11 | ],
12 | "__requires": [
13 | {
14 | "type": "grafana",
15 | "id": "grafana",
16 | "name": "Grafana",
17 | "version": "5.1.3"
18 | },
19 | {
20 | "type": "datasource",
21 | "id": "grafana-mongodb-datasource",
22 | "name": "MongoDB",
23 | "version": "0.8.1"
24 | },
25 | {
26 | "type": "panel",
27 | "id": "graph",
28 | "name": "Graph",
29 | "version": "5.0.0"
30 | }
31 | ],
32 | "annotations": {
33 | "list": [
34 | {
35 | "builtIn": 1,
36 | "datasource": "-- Grafana --",
37 | "enable": true,
38 | "hide": true,
39 | "iconColor": "rgba(0, 211, 255, 1)",
40 | "name": "Annotations & Alerts",
41 | "type": "dashboard"
42 | }
43 | ]
44 | },
45 | "editable": true,
46 | "gnetId": null,
47 | "graphTooltip": 0,
48 | "id": null,
49 | "iteration": 1535309830712,
50 | "links": [],
51 | "panels": [
52 | {
53 | "aliasColors": {},
54 | "bars": false,
55 | "dashLength": 10,
56 | "dashes": false,
57 | "datasource": "${DS_RPI_- ATLAS}",
58 | "fill": 1,
59 | "gridPos": {
60 | "h": 14,
61 | "w": 24,
62 | "x": 0,
63 | "y": 0
64 | },
65 | "id": 1,
66 | "legend": {
67 | "avg": false,
68 | "current": false,
69 | "max": false,
70 | "min": false,
71 | "show": true,
72 | "total": false,
73 | "values": false
74 | },
75 | "lines": true,
76 | "linewidth": 1,
77 | "links": [],
78 | "nullPointMode": "null",
79 | "percentage": false,
80 | "pointradius": 5,
81 | "points": false,
82 | "renderer": "flot",
83 | "seriesOverrides": [
84 | {
85 | "alias": "value"
86 | }
87 | ],
88 | "spaceLength": 10,
89 | "stack": false,
90 | "steppedLine": false,
91 | "targets": [
92 | {
93 | "groupByAliases": [],
94 | "groupByColumns": [],
95 | "metricAggs": [
96 | {
97 | "column": "value",
98 | "type": "avg"
99 | }
100 | ],
101 | "rawQuery": true,
102 | "refId": "A",
103 | "resultFormat": "time_series",
104 | "target": "db.sensor_value.aggregate( [ \n{ \"$match\" : { \"sensor_type\" : \"$var_sensor_type\", \"host_name\" : \"$var_host\" , \"sensor_name\" : \"ep\" , \"ts\" : { \"$gte\" : \"$from\", \"$lt\" : \"$to\" }}},\n{ \"$bucketAuto\" : { \"groupBy\" : \"$ts\", \n \"buckets\" : \"$dateBucketCount\", \n \"output\" : { \"maxValue\" : { \"$max\" : \"$sensor_value\" } } } }, \n{ \"$project\" : { \"name\" : \"ep\", \"value\" : \"$maxValue\", \"ts\" : \"$_id.min\", \"_id\" : 0 } } ] \n,{ \"allowDiskUse\" : true } )",
105 | "timeInterval": "auto_gf",
106 | "type": "timeserie",
107 | "whereClauses": []
108 | },
109 | {
110 | "rawQuery": true,
111 | "refId": "B",
112 | "target": "db.sensor_value.aggregate( [ \n{ \"$match\" : { \"sensor_type\" : \"$var_sensor_type\", \"host_name\" : \"$var_host\" , \"sensor_name\" : \"lg-DS18B20\" , \"ts\" : { \"$gte\" : \"$from\", \"$lt\" : \"$to\" }}},\n{ \"$bucketAuto\" : { \"groupBy\" : \"$ts\", \n \"buckets\" : \"$dateBucketCount\", \n \"output\" : { \"maxValue\" : { \"$max\" : \"$sensor_value\" } } } }, \n{ \"$project\" : { \"name\" : \"DS18B20\", \"value\" : \"$maxValue\", \"ts\" : \"$_id.min\", \"_id\" : 0 } } ] \n,{ \"allowDiskUse\" : true })",
113 | "type": "timeserie"
114 | }
115 | ],
116 | "thresholds": [],
117 | "timeFrom": null,
118 | "timeShift": null,
119 | "title": "Sensor Value",
120 | "tooltip": {
121 | "shared": true,
122 | "sort": 0,
123 | "value_type": "individual"
124 | },
125 | "type": "graph",
126 | "xaxis": {
127 | "buckets": null,
128 | "mode": "time",
129 | "name": null,
130 | "show": true,
131 | "values": []
132 | },
133 | "yaxes": [
134 | {
135 | "format": "none",
136 | "label": null,
137 | "logBase": 1,
138 | "max": null,
139 | "min": null,
140 | "show": true
141 | },
142 | {
143 | "format": "short",
144 | "label": null,
145 | "logBase": 1,
146 | "max": null,
147 | "min": null,
148 | "show": true
149 | }
150 | ],
151 | "yaxis": {
152 | "align": false,
153 | "alignLevel": null
154 | }
155 | }
156 | ],
157 | "refresh": false,
158 | "schemaVersion": 16,
159 | "style": "dark",
160 | "tags": [],
161 | "templating": {
162 | "list": [
163 | {
164 | "allValue": null,
165 | "current": {},
166 | "datasource": "${DS_RPI_- ATLAS}",
167 | "hide": 0,
168 | "includeAll": false,
169 | "label": "host",
170 | "multi": false,
171 | "name": "var_host",
172 | "options": [],
173 | "query": "db.sensor_value.aggregate ( [ { \"$group\" : { \"_id\" : \"$host_name\" } } ] ) ",
174 | "refresh": 1,
175 | "regex": "",
176 | "sort": 1,
177 | "tagValuesQuery": "",
178 | "tags": [],
179 | "tagsQuery": "",
180 | "type": "query",
181 | "useTags": false
182 | },
183 | {
184 | "allValue": null,
185 | "current": {},
186 | "datasource": "${DS_RPI_- ATLAS}",
187 | "hide": 0,
188 | "includeAll": false,
189 | "label": "sensor",
190 | "multi": false,
191 | "name": "var_sensor_type",
192 | "options": [],
193 | "query": "db.sensor_value.aggregate ( [ { \"$group\" : { \"_id\" : \"$sensor_type\" } } ] )",
194 | "refresh": 1,
195 | "regex": "",
196 | "sort": 1,
197 | "tagValuesQuery": "",
198 | "tags": [],
199 | "tagsQuery": "",
200 | "type": "query",
201 | "useTags": false
202 | }
203 | ]
204 | },
205 | "time": {
206 | "from": "now/M",
207 | "to": "now"
208 | },
209 | "timepicker": {
210 | "refresh_intervals": [
211 | "5s",
212 | "10s",
213 | "30s",
214 | "1m",
215 | "5m",
216 | "15m",
217 | "30m",
218 | "1h",
219 | "2h",
220 | "1d"
221 | ],
222 | "time_options": [
223 | "5m",
224 | "15m",
225 | "1h",
226 | "6h",
227 | "12h",
228 | "24h",
229 | "2d",
230 | "7d",
231 | "30d"
232 | ]
233 | },
234 | "timezone": "",
235 | "title": "RPI Mongo Bucket - Atlas Temp",
236 | "uid": "000000015",
237 | "version": 7
238 | }
--------------------------------------------------------------------------------
/dist/test/datasource.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../../src/datasource.js"],"names":["GenericDatasource","instanceSettings","$q","backendSrv","templateSrv","type","url","name","db","jsonData","mongodb_url","mongodb_db","q","withCredentials","headers","basicAuth","length","options","query","buildQueryParameters","targets","filter","t","hide","when","data","doRequest","method","then","response","status","message","title","display_status","replace","annotation","annotationQuery","range","datasource","enable","iconColor","rangeRaw","$$status","result","$$config","config","interpolated","target","mapToTextValue","map","d","i","text","value","isObject","datasourceRequest","scopedVars","refId"],"mappings":"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","file":"datasource.js","sourcesContent":["import _ from \"lodash\";\n\nexport class GenericDatasource {\n\n constructor(instanceSettings, $q, backendSrv, templateSrv) {\n this.type = instanceSettings.type;\n this.url = instanceSettings.url;\n this.name = instanceSettings.name;\n this.db = { 'url' : instanceSettings.jsonData.mongodb_url, 'db' : instanceSettings.jsonData.mongodb_db }\n this.q = $q;\n this.backendSrv = backendSrv;\n this.templateSrv = templateSrv;\n this.withCredentials = instanceSettings.withCredentials;\n this.headers = {'Content-Type': 'application/json'};\n if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) {\n this.headers['Authorization'] = instanceSettings.basicAuth;\n }\n }\n\n query(options) {\n var query = this.buildQueryParameters(options);\n query.targets = query.targets.filter(t => !t.hide);\n query.db = this.db\n\n if (query.targets.length <= 0) {\n return this.q.when({data: []});\n }\n\n return this.doRequest({\n url: this.url + '/query',\n data: query,\n method: 'POST'\n });\n }\n\n testDatasource() {\n return this.doRequest({\n url: this.url + '/',\n data : { db : this.db },\n method: 'POST',\n }).then(response => {\n if (response.status === 200) {\n return { status: response.data.status, message: response.data.message, title: response.data.display_status };\n }\n });\n }\n\n annotationQuery(options) {\n var query = this.templateSrv.replace(options.annotation.query, {}, 'glob');\n var annotationQuery = {\n range: options.range,\n annotation: {\n name: options.annotation.name,\n datasource: options.annotation.datasource,\n enable: options.annotation.enable,\n iconColor: options.annotation.iconColor,\n query: query\n },\n rangeRaw: options.rangeRaw\n };\n\n return this.doRequest({\n url: this.url + '/annotations',\n method: 'POST',\n data: annotationQuery\n }).then(result => {\n response.data.$$status = result.status;\n response.data.$$config = result.config;\n return result.data;\n });\n }\n\n metricFindQuery(query) {\n var interpolated = {\n target: this.templateSrv.replace(query, null, '')\n };\n interpolated.db = this.db\n\n return this.doRequest({\n url: this.url + '/search',\n data: interpolated,\n method: 'POST',\n }).then(this.mapToTextValue);\n }\n\n mapToTextValue(result) {\n return _.map(result.data, (d, i) => {\n if (d && d.text && d.value) {\n return { text: d.text, value: d.value };\n } else if (_.isObject(d)) {\n return { text: d, value: i};\n }\n return { text: d, value: d };\n });\n }\n\n doRequest(options) {\n options.withCredentials = this.withCredentials;\n options.headers = this.headers;\n\n return this.backendSrv.datasourceRequest(options);\n }\n\n buildQueryParameters(options) {\n //remove place holder targets\n options.targets = _.filter(options.targets, target => {\n return target.target !== 'select metric';\n });\n\n var targets = _.map(options.targets, target => {\n return {\n target: this.templateSrv.replace(target.target, options.scopedVars, ''),\n refId: target.refId,\n hide: target.hide,\n type: target.type || 'timeserie' \n };\n });\n\n options.targets = targets;\n\n return options;\n }\n}\n"]}
--------------------------------------------------------------------------------
/dist/datasource.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../src/datasource.js"],"names":["_","GenericDatasource","instanceSettings","$q","backendSrv","templateSrv","type","url","name","db","jsonData","mongodb_url","mongodb_db","q","withCredentials","headers","basicAuth","length","options","query","buildQueryParameters","targets","filter","t","hide","when","data","doRequest","method","then","response","status","message","title","display_status","replace","annotation","annotationQuery","range","datasource","enable","iconColor","rangeRaw","$$status","result","$$config","config","interpolated","target","mapToTextValue","map","d","i","text","value","isObject","datasourceRequest","scopedVars","refId"],"mappings":"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","file":"datasource.js","sourcesContent":["import _ from \"lodash\";\n\nexport class GenericDatasource {\n\n constructor(instanceSettings, $q, backendSrv, templateSrv) {\n this.type = instanceSettings.type;\n this.url = instanceSettings.url;\n this.name = instanceSettings.name;\n this.db = { 'url' : instanceSettings.jsonData.mongodb_url, 'db' : instanceSettings.jsonData.mongodb_db }\n this.q = $q;\n this.backendSrv = backendSrv;\n this.templateSrv = templateSrv;\n this.withCredentials = instanceSettings.withCredentials;\n this.headers = {'Content-Type': 'application/json'};\n if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) {\n this.headers['Authorization'] = instanceSettings.basicAuth;\n }\n }\n\n query(options) {\n var query = this.buildQueryParameters(options);\n query.targets = query.targets.filter(t => !t.hide);\n query.db = this.db\n\n if (query.targets.length <= 0) {\n return this.q.when({data: []});\n }\n\n return this.doRequest({\n url: this.url + '/query',\n data: query,\n method: 'POST'\n });\n }\n\n testDatasource() {\n return this.doRequest({\n url: this.url + '/',\n data : { db : this.db },\n method: 'POST',\n }).then(response => {\n if (response.status === 200) {\n return { status: response.data.status, message: response.data.message, title: response.data.display_status };\n }\n });\n }\n\n annotationQuery(options) {\n var query = this.templateSrv.replace(options.annotation.query, {}, 'glob');\n var annotationQuery = {\n range: options.range,\n annotation: {\n name: options.annotation.name,\n datasource: options.annotation.datasource,\n enable: options.annotation.enable,\n iconColor: options.annotation.iconColor,\n query: query\n },\n rangeRaw: options.rangeRaw\n };\n\n return this.doRequest({\n url: this.url + '/annotations',\n method: 'POST',\n data: annotationQuery\n }).then(result => {\n response.data.$$status = result.status;\n response.data.$$config = result.config;\n return result.data;\n });\n }\n\n metricFindQuery(query) {\n var interpolated = {\n target: this.templateSrv.replace(query, null, '')\n };\n interpolated.db = this.db\n\n return this.doRequest({\n url: this.url + '/search',\n data: interpolated,\n method: 'POST',\n }).then(this.mapToTextValue);\n }\n\n mapToTextValue(result) {\n return _.map(result.data, (d, i) => {\n if (d && d.text && d.value) {\n return { text: d.text, value: d.value };\n } else if (_.isObject(d)) {\n return { text: d, value: i};\n }\n return { text: d, value: d };\n });\n }\n\n doRequest(options) {\n options.withCredentials = this.withCredentials;\n options.headers = this.headers;\n\n return this.backendSrv.datasourceRequest(options);\n }\n\n buildQueryParameters(options) {\n //remove place holder targets\n options.targets = _.filter(options.targets, target => {\n return target.target !== 'select metric';\n });\n\n var targets = _.map(options.targets, target => {\n return {\n target: this.templateSrv.replace(target.target, options.scopedVars, ''),\n refId: target.refId,\n hide: target.hide,\n type: target.type || 'timeserie' \n };\n });\n\n options.targets = targets;\n\n return options;\n }\n}\n"]}
--------------------------------------------------------------------------------
/dist/test/spec/datasource_spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _module = require("../module");
4 |
5 | var _q = require("q");
6 |
7 | var _q2 = _interopRequireDefault(_q);
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe('GenericDatasource', function () {
12 | var ctx = {};
13 |
14 | beforeEach(function () {
15 | ctx.$q = _q2.default;
16 | ctx.backendSrv = {};
17 | ctx.templateSrv = {};
18 | var jsonData = { mongodb_url: 'mongodb://localhost:27017', mongodb_db: 'test_db' };
19 | ctx.ds = new _module.Datasource({ jsonData: jsonData }, ctx.$q, ctx.backendSrv, ctx.templateSrv);
20 | });
21 |
22 | it('should return an empty array when no targets are set', function (done) {
23 | ctx.ds.query({ targets: [] }).then(function (result) {
24 | expect(result.data).to.have.length(0);
25 | done();
26 | });
27 | });
28 |
29 | it('should return the server results when a target is set', function (done) {
30 | ctx.backendSrv.datasourceRequest = function (request) {
31 | return ctx.$q.when({
32 | _request: request,
33 | data: [{
34 | target: 'X',
35 | datapoints: [1, 2, 3]
36 | }]
37 | });
38 | };
39 |
40 | ctx.templateSrv.replace = function (data) {
41 | return data;
42 | };
43 |
44 | ctx.ds.query({ targets: ['hits'] }).then(function (result) {
45 | expect(result._request.data.targets).to.have.length(1);
46 | expect(result._request.data.db.url).to.equal('mongodb://localhost:27017');
47 | expect(result._request.data.db.db).to.equal('test_db');
48 |
49 | var series = result.data[0];
50 | expect(series.target).to.equal('X');
51 | expect(series.datapoints).to.have.length(3);
52 | done();
53 | });
54 | });
55 |
56 | it('should return the metric results when a target is null', function (done) {
57 | ctx.backendSrv.datasourceRequest = function (request) {
58 | return ctx.$q.when({
59 | _request: request,
60 | data: ["metric_0", "metric_1", "metric_2"]
61 | });
62 | };
63 |
64 | ctx.templateSrv.replace = function (data) {
65 | return data;
66 | };
67 |
68 | ctx.ds.metricFindQuery({ target: null }).then(function (result) {
69 | expect(result).to.have.length(3);
70 | expect(result[0].text).to.equal('metric_0');
71 | expect(result[0].value).to.equal('metric_0');
72 | expect(result[1].text).to.equal('metric_1');
73 | expect(result[1].value).to.equal('metric_1');
74 | expect(result[2].text).to.equal('metric_2');
75 | expect(result[2].value).to.equal('metric_2');
76 | done();
77 | });
78 | });
79 |
80 | it('should return the metric target results when a target is set', function (done) {
81 | ctx.backendSrv.datasourceRequest = function (request) {
82 | var target = request.data.target;
83 | var result = [target + "_0", target + "_1", target + "_2"];
84 |
85 | return ctx.$q.when({
86 | _request: request,
87 | data: result
88 | });
89 | };
90 |
91 | ctx.templateSrv.replace = function (data) {
92 | return data;
93 | };
94 |
95 | ctx.ds.metricFindQuery('search').then(function (result) {
96 | expect(result).to.have.length(3);
97 | expect(result[0].text).to.equal('search_0');
98 | expect(result[0].value).to.equal('search_0');
99 | expect(result[1].text).to.equal('search_1');
100 | expect(result[1].value).to.equal('search_1');
101 | expect(result[2].text).to.equal('search_2');
102 | expect(result[2].value).to.equal('search_2');
103 | done();
104 | });
105 | });
106 |
107 | it('should return the metric results when the target is an empty string', function (done) {
108 | ctx.backendSrv.datasourceRequest = function (request) {
109 | return ctx.$q.when({
110 | _request: request,
111 | data: ["metric_0", "metric_1", "metric_2"]
112 | });
113 | };
114 |
115 | ctx.templateSrv.replace = function (data) {
116 | return data;
117 | };
118 |
119 | ctx.ds.metricFindQuery('').then(function (result) {
120 | expect(result).to.have.length(3);
121 | expect(result[0].text).to.equal('metric_0');
122 | expect(result[0].value).to.equal('metric_0');
123 | expect(result[1].text).to.equal('metric_1');
124 | expect(result[1].value).to.equal('metric_1');
125 | expect(result[2].text).to.equal('metric_2');
126 | expect(result[2].value).to.equal('metric_2');
127 | done();
128 | });
129 | });
130 |
131 | it('should return the metric results when the args are an empty object', function (done) {
132 | ctx.backendSrv.datasourceRequest = function (request) {
133 | return ctx.$q.when({
134 | _request: request,
135 | data: ["metric_0", "metric_1", "metric_2"]
136 | });
137 | };
138 |
139 | ctx.templateSrv.replace = function (data) {
140 | return data;
141 | };
142 |
143 | ctx.ds.metricFindQuery().then(function (result) {
144 | expect(result).to.have.length(3);
145 | expect(result[0].text).to.equal('metric_0');
146 | expect(result[0].value).to.equal('metric_0');
147 | expect(result[1].text).to.equal('metric_1');
148 | expect(result[1].value).to.equal('metric_1');
149 | expect(result[2].text).to.equal('metric_2');
150 | expect(result[2].value).to.equal('metric_2');
151 | done();
152 | });
153 | });
154 |
155 | it('should return the metric target results when the args are a string', function (done) {
156 | ctx.backendSrv.datasourceRequest = function (request) {
157 | var target = request.data.target;
158 | var result = [target + "_0", target + "_1", target + "_2"];
159 |
160 | return ctx.$q.when({
161 | _request: request,
162 | data: result
163 | });
164 | };
165 |
166 | ctx.templateSrv.replace = function (data) {
167 | return data;
168 | };
169 |
170 | ctx.ds.metricFindQuery('search').then(function (result) {
171 | expect(result).to.have.length(3);
172 | expect(result[0].text).to.equal('search_0');
173 | expect(result[0].value).to.equal('search_0');
174 | expect(result[1].text).to.equal('search_1');
175 | expect(result[1].value).to.equal('search_1');
176 | expect(result[2].text).to.equal('search_2');
177 | expect(result[2].value).to.equal('search_2');
178 | done();
179 | });
180 | });
181 |
182 | it('should return data as text and as value', function (done) {
183 | var result = ctx.ds.mapToTextValue({ data: ["zero", "one", "two"] });
184 |
185 | expect(result).to.have.length(3);
186 | expect(result[0].text).to.equal('zero');
187 | expect(result[0].value).to.equal('zero');
188 | expect(result[1].text).to.equal('one');
189 | expect(result[1].value).to.equal('one');
190 | expect(result[2].text).to.equal('two');
191 | expect(result[2].value).to.equal('two');
192 | done();
193 | });
194 |
195 | it('should return text as text and value as value', function (done) {
196 | var data = [{ text: "zero", value: "value_0" }, { text: "one", value: "value_1" }, { text: "two", value: "value_2" }];
197 |
198 | var result = ctx.ds.mapToTextValue({ data: data });
199 |
200 | expect(result).to.have.length(3);
201 | expect(result[0].text).to.equal('zero');
202 | expect(result[0].value).to.equal('value_0');
203 | expect(result[1].text).to.equal('one');
204 | expect(result[1].value).to.equal('value_1');
205 | expect(result[2].text).to.equal('two');
206 | expect(result[2].value).to.equal('value_2');
207 | done();
208 | });
209 |
210 | it('should return data as text and index as value', function (done) {
211 | var data = [{ a: "zero", b: "value_0" }, { a: "one", b: "value_1" }, { a: "two", b: "value_2" }];
212 |
213 | var result = ctx.ds.mapToTextValue({ data: data });
214 |
215 | expect(result).to.have.length(3);
216 | expect(result[0].text).to.equal(data[0]);
217 | expect(result[0].value).to.equal(0);
218 | expect(result[1].text).to.equal(data[1]);
219 | expect(result[1].value).to.equal(1);
220 | expect(result[2].text).to.equal(data[2]);
221 | expect(result[2].value).to.equal(2);
222 | done();
223 | });
224 | });
225 | //# sourceMappingURL=datasource_spec.js.map
226 |
--------------------------------------------------------------------------------
/spec/datasource_spec.js:
--------------------------------------------------------------------------------
1 | import {Datasource} from "../module";
2 | import Q from "q";
3 |
4 | describe('GenericDatasource', function() {
5 | var ctx = {};
6 |
7 | beforeEach(function() {
8 | ctx.$q = Q;
9 | ctx.backendSrv = {};
10 | ctx.templateSrv = {};
11 | var jsonData = { mongodb_url : 'mongodb://localhost:27017', mongodb_db : 'test_db' }
12 | ctx.ds = new Datasource({jsonData : jsonData}, ctx.$q, ctx.backendSrv, ctx.templateSrv);
13 | });
14 |
15 | it('should return an empty array when no targets are set', function(done) {
16 | ctx.ds.query({targets: []}).then(function(result) {
17 | expect(result.data).to.have.length(0);
18 | done();
19 | });
20 | });
21 |
22 | it('should return the server results when a target is set', function(done) {
23 | ctx.backendSrv.datasourceRequest = function(request) {
24 | return ctx.$q.when({
25 | _request: request,
26 | data: [
27 | {
28 | target: 'X',
29 | datapoints: [1, 2, 3]
30 | }
31 | ]
32 | });
33 | };
34 |
35 | ctx.templateSrv.replace = function(data) {
36 | return data;
37 | }
38 |
39 | ctx.ds.query({targets: ['hits']}).then(function(result) {
40 | expect(result._request.data.targets).to.have.length(1);
41 | expect(result._request.data.db.url).to.equal('mongodb://localhost:27017');
42 | expect(result._request.data.db.db).to.equal('test_db');
43 |
44 | var series = result.data[0];
45 | expect(series.target).to.equal('X');
46 | expect(series.datapoints).to.have.length(3);
47 | done();
48 | });
49 | });
50 |
51 | it ('should return the metric results when a target is null', function(done) {
52 | ctx.backendSrv.datasourceRequest = function(request) {
53 | return ctx.$q.when({
54 | _request: request,
55 | data: [
56 | "metric_0",
57 | "metric_1",
58 | "metric_2",
59 | ]
60 | });
61 | };
62 |
63 | ctx.templateSrv.replace = function(data) {
64 | return data;
65 | }
66 |
67 | ctx.ds.metricFindQuery({target: null}).then(function(result) {
68 | expect(result).to.have.length(3);
69 | expect(result[0].text).to.equal('metric_0');
70 | expect(result[0].value).to.equal('metric_0');
71 | expect(result[1].text).to.equal('metric_1');
72 | expect(result[1].value).to.equal('metric_1');
73 | expect(result[2].text).to.equal('metric_2');
74 | expect(result[2].value).to.equal('metric_2');
75 | done();
76 | });
77 | });
78 |
79 | it ('should return the metric target results when a target is set', function(done) {
80 | ctx.backendSrv.datasourceRequest = function(request) {
81 | var target = request.data.target;
82 | var result = [target + "_0", target + "_1", target + "_2"];
83 |
84 | return ctx.$q.when({
85 | _request: request,
86 | data: result
87 | });
88 | };
89 |
90 | ctx.templateSrv.replace = function(data) {
91 | return data;
92 | }
93 |
94 | ctx.ds.metricFindQuery('search').then(function(result) {
95 | expect(result).to.have.length(3);
96 | expect(result[0].text).to.equal('search_0');
97 | expect(result[0].value).to.equal('search_0');
98 | expect(result[1].text).to.equal('search_1');
99 | expect(result[1].value).to.equal('search_1');
100 | expect(result[2].text).to.equal('search_2');
101 | expect(result[2].value).to.equal('search_2');
102 | done();
103 | });
104 | });
105 |
106 | it ('should return the metric results when the target is an empty string', function(done) {
107 | ctx.backendSrv.datasourceRequest = function(request) {
108 | return ctx.$q.when({
109 | _request: request,
110 | data: [
111 | "metric_0",
112 | "metric_1",
113 | "metric_2",
114 | ]
115 | });
116 | };
117 |
118 | ctx.templateSrv.replace = function(data) {
119 | return data;
120 | }
121 |
122 | ctx.ds.metricFindQuery('').then(function(result) {
123 | expect(result).to.have.length(3);
124 | expect(result[0].text).to.equal('metric_0');
125 | expect(result[0].value).to.equal('metric_0');
126 | expect(result[1].text).to.equal('metric_1');
127 | expect(result[1].value).to.equal('metric_1');
128 | expect(result[2].text).to.equal('metric_2');
129 | expect(result[2].value).to.equal('metric_2');
130 | done();
131 | });
132 | });
133 |
134 | it ('should return the metric results when the args are an empty object', function(done) {
135 | ctx.backendSrv.datasourceRequest = function(request) {
136 | return ctx.$q.when({
137 | _request: request,
138 | data: [
139 | "metric_0",
140 | "metric_1",
141 | "metric_2",
142 | ]
143 | });
144 | };
145 |
146 | ctx.templateSrv.replace = function(data) {
147 | return data;
148 | }
149 |
150 | ctx.ds.metricFindQuery().then(function(result) {
151 | expect(result).to.have.length(3);
152 | expect(result[0].text).to.equal('metric_0');
153 | expect(result[0].value).to.equal('metric_0');
154 | expect(result[1].text).to.equal('metric_1');
155 | expect(result[1].value).to.equal('metric_1');
156 | expect(result[2].text).to.equal('metric_2');
157 | expect(result[2].value).to.equal('metric_2');
158 | done();
159 | });
160 | });
161 |
162 | it ('should return the metric target results when the args are a string', function(done) {
163 | ctx.backendSrv.datasourceRequest = function(request) {
164 | var target = request.data.target;
165 | var result = [target + "_0", target + "_1", target + "_2"];
166 |
167 | return ctx.$q.when({
168 | _request: request,
169 | data: result
170 | });
171 | };
172 |
173 | ctx.templateSrv.replace = function(data) {
174 | return data;
175 | }
176 |
177 | ctx.ds.metricFindQuery('search').then(function(result) {
178 | expect(result).to.have.length(3);
179 | expect(result[0].text).to.equal('search_0');
180 | expect(result[0].value).to.equal('search_0');
181 | expect(result[1].text).to.equal('search_1');
182 | expect(result[1].value).to.equal('search_1');
183 | expect(result[2].text).to.equal('search_2');
184 | expect(result[2].value).to.equal('search_2');
185 | done();
186 | });
187 | });
188 |
189 | it ('should return data as text and as value', function(done) {
190 | var result = ctx.ds.mapToTextValue({data: ["zero", "one", "two"]});
191 |
192 | expect(result).to.have.length(3);
193 | expect(result[0].text).to.equal('zero');
194 | expect(result[0].value).to.equal('zero');
195 | expect(result[1].text).to.equal('one');
196 | expect(result[1].value).to.equal('one');
197 | expect(result[2].text).to.equal('two');
198 | expect(result[2].value).to.equal('two');
199 | done();
200 | });
201 |
202 | it ('should return text as text and value as value', function(done) {
203 | var data = [
204 | {text: "zero", value: "value_0"},
205 | {text: "one", value: "value_1"},
206 | {text: "two", value: "value_2"},
207 | ];
208 |
209 | var result = ctx.ds.mapToTextValue({data: data});
210 |
211 | expect(result).to.have.length(3);
212 | expect(result[0].text).to.equal('zero');
213 | expect(result[0].value).to.equal('value_0');
214 | expect(result[1].text).to.equal('one');
215 | expect(result[1].value).to.equal('value_1');
216 | expect(result[2].text).to.equal('two');
217 | expect(result[2].value).to.equal('value_2');
218 | done();
219 | });
220 |
221 | it ('should return data as text and index as value', function(done) {
222 | var data = [
223 | {a: "zero", b: "value_0"},
224 | {a: "one", b: "value_1"},
225 | {a: "two", b: "value_2"},
226 | ];
227 |
228 | var result = ctx.ds.mapToTextValue({data: data});
229 |
230 | expect(result).to.have.length(3);
231 | expect(result[0].text).to.equal(data[0]);
232 | expect(result[0].value).to.equal(0);
233 | expect(result[1].text).to.equal(data[1]);
234 | expect(result[1].value).to.equal(1);
235 | expect(result[2].text).to.equal(data[2]);
236 | expect(result[2].value).to.equal(2);
237 | done();
238 | });
239 | });
240 |
--------------------------------------------------------------------------------
/examples/RPI Mongo Bucket - Atlas CS.json:
--------------------------------------------------------------------------------
1 | {
2 | "__inputs": [
3 | {
4 | "name": "DS_RPI_- ATLAS",
5 | "label": "RPI - Atlas",
6 | "description": "",
7 | "type": "datasource",
8 | "pluginId": "grafana-mongodb-datasource",
9 | "pluginName": "MongoDB"
10 | }
11 | ],
12 | "__requires": [
13 | {
14 | "type": "grafana",
15 | "id": "grafana",
16 | "name": "Grafana",
17 | "version": "5.1.3"
18 | },
19 | {
20 | "type": "datasource",
21 | "id": "grafana-mongodb-datasource",
22 | "name": "MongoDB",
23 | "version": "0.8.1"
24 | },
25 | {
26 | "type": "panel",
27 | "id": "graph",
28 | "name": "Graph",
29 | "version": "5.0.0"
30 | }
31 | ],
32 | "annotations": {
33 | "list": [
34 | {
35 | "builtIn": 1,
36 | "datasource": "-- Grafana --",
37 | "enable": true,
38 | "hide": true,
39 | "iconColor": "rgba(0, 211, 255, 1)",
40 | "name": "Annotations & Alerts",
41 | "type": "dashboard"
42 | }
43 | ]
44 | },
45 | "editable": true,
46 | "gnetId": null,
47 | "graphTooltip": 0,
48 | "id": null,
49 | "iteration": 1535309747386,
50 | "links": [],
51 | "panels": [
52 | {
53 | "aliasColors": {},
54 | "bars": false,
55 | "dashLength": 10,
56 | "dashes": false,
57 | "datasource": "${DS_RPI_- ATLAS}",
58 | "fill": 1,
59 | "gridPos": {
60 | "h": 11,
61 | "w": 24,
62 | "x": 0,
63 | "y": 0
64 | },
65 | "id": 1,
66 | "legend": {
67 | "avg": false,
68 | "current": false,
69 | "max": false,
70 | "min": false,
71 | "show": true,
72 | "total": false,
73 | "values": false
74 | },
75 | "lines": true,
76 | "linewidth": 1,
77 | "links": [],
78 | "nullPointMode": "null",
79 | "percentage": false,
80 | "pointradius": 5,
81 | "points": false,
82 | "renderer": "flot",
83 | "seriesOverrides": [
84 | {
85 | "alias": "value"
86 | }
87 | ],
88 | "spaceLength": 10,
89 | "stack": false,
90 | "steppedLine": false,
91 | "targets": [
92 | {
93 | "rawQuery": true,
94 | "refId": "B",
95 | "target": "db.sensor_value.aggregate( [ \n{ \"$match\" : { \"sensor_type\" : \"$var_sensor_type\", \"host_name\" : \"$var_host\" , \"sensor_name\" : \"ep\" , \"sensor_type\" : \"temperature\" , \"ts\" : { \"$gte\" : \"$from\", \"$lt\" : \"$to\" }}},\n{ \"$bucketAuto\" : { \"groupBy\" : \"$ts\", \n \"buckets\" : \"$dateBucketCount\", \n \"output\" : { \"maxValue\" : { \"$max\" : \"$sensor_value\" } } } }, \n{ \"$project\" : { \"name\" : \"Enviro pHAT\", \"value\" : \"$maxValue\", \"ts\" : \"$_id.min\", \"_id\" : 0 } } ] )",
96 | "type": "timeserie"
97 | }
98 | ],
99 | "thresholds": [],
100 | "timeFrom": null,
101 | "timeShift": null,
102 | "title": "Sensor Value",
103 | "tooltip": {
104 | "shared": true,
105 | "sort": 0,
106 | "value_type": "individual"
107 | },
108 | "type": "graph",
109 | "xaxis": {
110 | "buckets": null,
111 | "mode": "time",
112 | "name": null,
113 | "show": true,
114 | "values": []
115 | },
116 | "yaxes": [
117 | {
118 | "format": "none",
119 | "label": null,
120 | "logBase": 1,
121 | "max": null,
122 | "min": null,
123 | "show": true
124 | },
125 | {
126 | "format": "short",
127 | "label": null,
128 | "logBase": 1,
129 | "max": null,
130 | "min": null,
131 | "show": true
132 | }
133 | ],
134 | "yaxis": {
135 | "align": false,
136 | "alignLevel": null
137 | }
138 | },
139 | {
140 | "aliasColors": {},
141 | "bars": true,
142 | "dashLength": 10,
143 | "dashes": false,
144 | "datasource": "${DS_RPI_- ATLAS}",
145 | "fill": 1,
146 | "gridPos": {
147 | "h": 7,
148 | "w": 24,
149 | "x": 0,
150 | "y": 11
151 | },
152 | "id": 2,
153 | "legend": {
154 | "avg": false,
155 | "current": false,
156 | "max": false,
157 | "min": false,
158 | "show": true,
159 | "total": false,
160 | "values": false
161 | },
162 | "lines": false,
163 | "linewidth": 1,
164 | "links": [],
165 | "nullPointMode": "null",
166 | "percentage": false,
167 | "pointradius": 5,
168 | "points": false,
169 | "renderer": "flot",
170 | "seriesOverrides": [
171 | {
172 | "alias": "DS18B20",
173 | "color": "#eab839"
174 | }
175 | ],
176 | "spaceLength": 10,
177 | "stack": false,
178 | "steppedLine": false,
179 | "targets": [
180 | {
181 | "groupByAliases": [],
182 | "groupByColumns": [],
183 | "metricAggs": [
184 | {
185 | "column": "value",
186 | "type": "avg"
187 | }
188 | ],
189 | "rawQuery": true,
190 | "refId": "A",
191 | "resultFormat": "time_series",
192 | "target": "db.trend_value.aggregate( [ \n{ \"$match\" : { \"sensor_type\" : \"$var_sensor_type\", \"host_name\" : \"$var_host\" , \"sensor_name\" : \"ep\" , \"sensor_type\" : \"temperature\" , \"ts\" : { \"$gte\" : \"$from\", \"$lt\" : \"$to\" }}},\n{ \"$bucketAuto\" : { \"groupBy\" : \"$ts\", \n \"buckets\" : \"$dateBucketCount\", \n \"output\" : { \"maxValue\" : { \"$max\" : \"$trend_value\" } } } }, \n{ \"$project\" : { \"name\" : \"Enviro pHAT\", \"value\" : \"$maxValue\", \"ts\" : \"$_id.min\", \"_id\" : 0 } } ] )",
193 | "timeInterval": "auto_gf",
194 | "type": "timeserie",
195 | "whereClauses": []
196 | }
197 | ],
198 | "thresholds": [
199 | {
200 | "colorMode": "critical",
201 | "fill": true,
202 | "line": true,
203 | "op": "lt",
204 | "value": 0
205 | },
206 | {
207 | "colorMode": "ok",
208 | "fill": true,
209 | "line": true,
210 | "op": "gt",
211 | "value": 0
212 | }
213 | ],
214 | "timeFrom": null,
215 | "timeShift": null,
216 | "title": "Trend",
217 | "tooltip": {
218 | "shared": true,
219 | "sort": 0,
220 | "value_type": "individual"
221 | },
222 | "type": "graph",
223 | "xaxis": {
224 | "buckets": null,
225 | "mode": "time",
226 | "name": null,
227 | "show": true,
228 | "values": []
229 | },
230 | "yaxes": [
231 | {
232 | "decimals": 0,
233 | "format": "short",
234 | "label": null,
235 | "logBase": 1,
236 | "max": null,
237 | "min": null,
238 | "show": true
239 | },
240 | {
241 | "format": "short",
242 | "label": null,
243 | "logBase": 1,
244 | "max": null,
245 | "min": null,
246 | "show": true
247 | }
248 | ],
249 | "yaxis": {
250 | "align": false,
251 | "alignLevel": null
252 | }
253 | }
254 | ],
255 | "refresh": "5s",
256 | "schemaVersion": 16,
257 | "style": "dark",
258 | "tags": [],
259 | "templating": {
260 | "list": [
261 | {
262 | "allValue": null,
263 | "current": {},
264 | "datasource": "${DS_RPI_- ATLAS}",
265 | "hide": 0,
266 | "includeAll": false,
267 | "label": "host",
268 | "multi": false,
269 | "name": "var_host",
270 | "options": [],
271 | "query": "db.sensor_value.aggregate ( [ { \"$group\" : { \"_id\" : \"$host_name\" } } ] ) ",
272 | "refresh": 1,
273 | "regex": "",
274 | "sort": 1,
275 | "tagValuesQuery": "",
276 | "tags": [],
277 | "tagsQuery": "",
278 | "type": "query",
279 | "useTags": false
280 | },
281 | {
282 | "allValue": null,
283 | "current": {},
284 | "datasource": "${DS_RPI_- ATLAS}",
285 | "hide": 0,
286 | "includeAll": false,
287 | "label": "sensor",
288 | "multi": false,
289 | "name": "var_sensor_type",
290 | "options": [],
291 | "query": "db.sensor_value.aggregate ( [ { \"$group\" : { \"_id\" : \"$sensor_type\" } } ] )",
292 | "refresh": 1,
293 | "regex": "",
294 | "sort": 1,
295 | "tagValuesQuery": "",
296 | "tags": [],
297 | "tagsQuery": "",
298 | "type": "query",
299 | "useTags": false
300 | }
301 | ]
302 | },
303 | "time": {
304 | "from": "now-15m",
305 | "to": "now"
306 | },
307 | "timepicker": {
308 | "refresh_intervals": [
309 | "5s",
310 | "10s",
311 | "30s",
312 | "1m",
313 | "5m",
314 | "15m",
315 | "30m",
316 | "1h",
317 | "2h",
318 | "1d"
319 | ],
320 | "time_options": [
321 | "5m",
322 | "15m",
323 | "1h",
324 | "6h",
325 | "12h",
326 | "24h",
327 | "2d",
328 | "7d",
329 | "30d"
330 | ]
331 | },
332 | "timezone": "",
333 | "title": "RPI Mongo Bucket - Atlas CS",
334 | "uid": "000000016",
335 | "version": 9
336 | }
--------------------------------------------------------------------------------
/debugging/grafana.ini:
--------------------------------------------------------------------------------
1 | ##################### Grafana Configuration Example #####################
2 | #
3 | # Everything has defaults so you only need to uncomment things you want to
4 | # change
5 |
6 | # possible values : production, development
7 | ; app_mode = production
8 |
9 | # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
10 | ; instance_name = ${HOSTNAME}
11 |
12 | #################################### Paths ####################################
13 | [paths]
14 | # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
15 | #
16 | data = /usr/local/var/lib/grafana
17 | #
18 | # Directory where grafana can store logs
19 | #
20 | logs = ./logs
21 | #
22 | # Directory where grafana will automatically scan and look for plugins
23 | #
24 | plugins = /Users/james/code/github/grafana/plugins
25 |
26 | #
27 | #################################### Server ####################################
28 | [server]
29 | # Protocol (http, https, socket)
30 | ;protocol = http
31 |
32 | # The ip address to bind to, empty will bind to all interfaces
33 | ;http_addr =
34 |
35 | # The http port to use
36 | ;http_port = 3000
37 |
38 | # The public facing domain name used to access grafana from a browser
39 | ;domain = localhost
40 |
41 | # Redirect to correct domain if host header does not match domain
42 | # Prevents DNS rebinding attacks
43 | ;enforce_domain = false
44 |
45 | # The full public facing url you use in browser, used for redirects and emails
46 | # If you use reverse proxy and sub path specify full url (with sub path)
47 | ;root_url = http://localhost:3000
48 |
49 | # Log web requests
50 | ;router_logging = false
51 |
52 | # the path relative working path
53 | ;static_root_path = public
54 |
55 | # enable gzip
56 | ;enable_gzip = false
57 |
58 | # https certs & key file
59 | ;cert_file =
60 | ;cert_key =
61 |
62 | # Unix socket path
63 | ;socket =
64 |
65 | #################################### Database ####################################
66 | [database]
67 | # You can configure the database connection by specifying type, host, name, user and password
68 | # as seperate properties or as on string using the url propertie.
69 |
70 | # Either "mysql", "postgres" or "sqlite3", it's your choice
71 | type = mysql
72 | host = 127.0.0.1:3306
73 | ;name = grafana
74 | ;user = root
75 | # If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""
76 | ;password =
77 |
78 | # Use either URL or the previous fields to configure the database
79 | # Example: mysql://user:secret@host:port/database
80 | ;url =
81 |
82 | # For "postgres" only, either "disable", "require" or "verify-full"
83 | ;ssl_mode = disable
84 |
85 | # For "sqlite3" only, path relative to data_path setting
86 | ;path = grafana.db
87 |
88 | # Max idle conn setting default is 2
89 | ;max_idle_conn = 2
90 |
91 | # Max conn setting default is 0 (mean not set)
92 | ;max_open_conn =
93 |
94 |
95 | #################################### Session ####################################
96 | [session]
97 | # Either "memory", "file", "redis", "mysql", "postgres", default is "file"
98 | ;provider = file
99 |
100 | # Provider config options
101 | # memory: not have any config yet
102 | # file: session dir path, is relative to grafana data_path
103 | # redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
104 | # mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
105 | # postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
106 | ;provider_config = sessions
107 |
108 | # Session cookie name
109 | ;cookie_name = grafana_sess
110 |
111 | # If you use session in https only, default is false
112 | ;cookie_secure = false
113 |
114 | # Session life time, default is 86400
115 | ;session_life_time = 86400
116 |
117 | #################################### Data proxy ###########################
118 | [dataproxy]
119 |
120 | # This enables data proxy logging, default is false
121 | ;logging = false
122 |
123 |
124 | #################################### Analytics ####################################
125 | [analytics]
126 | # Server reporting, sends usage counters to stats.grafana.org every 24 hours.
127 | # No ip addresses are being tracked, only simple counters to track
128 | # running instances, dashboard and error counts. It is very helpful to us.
129 | # Change this option to false to disable reporting.
130 | ;reporting_enabled = true
131 |
132 | # Set to false to disable all checks to https://grafana.net
133 | # for new vesions (grafana itself and plugins), check is used
134 | # in some UI views to notify that grafana or plugin update exists
135 | # This option does not cause any auto updates, nor send any information
136 | # only a GET request to http://grafana.com to get latest versions
137 | ;check_for_updates = true
138 |
139 | # Google Analytics universal tracking code, only enabled if you specify an id here
140 | ;google_analytics_ua_id =
141 |
142 | #################################### Security ####################################
143 | [security]
144 | # default admin user, created on startup
145 | ;admin_user = admin
146 |
147 | # default admin password, can be changed before first start of grafana, or in profile settings
148 | ;admin_password = admin
149 |
150 | # used for signing
151 | ;secret_key = SW2YcwTIb9zpOOhoPsMm
152 |
153 | # Auto-login remember days
154 | ;login_remember_days = 7
155 | ;cookie_username = grafana_user
156 | ;cookie_remember_name = grafana_remember
157 |
158 | # disable gravatar profile images
159 | ;disable_gravatar = false
160 |
161 | # data source proxy whitelist (ip_or_domain:port separated by spaces)
162 | ;data_source_proxy_whitelist =
163 |
164 | [snapshots]
165 | # snapshot sharing options
166 | ;external_enabled = true
167 | ;external_snapshot_url = https://snapshots-origin.raintank.io
168 | ;external_snapshot_name = Publish to snapshot.raintank.io
169 |
170 | # remove expired snapshot
171 | ;snapshot_remove_expired = true
172 |
173 | # remove snapshots after 90 days
174 | ;snapshot_TTL_days = 90
175 |
176 | #################################### Users ####################################
177 | [users]
178 | # disable user signup / registration
179 | ;allow_sign_up = true
180 |
181 | # Allow non admin users to create organizations
182 | ;allow_org_create = true
183 |
184 | # Set to true to automatically assign new users to the default organization (id 1)
185 | ;auto_assign_org = true
186 |
187 | # Default role new users will be automatically assigned (if disabled above is set to true)
188 | ;auto_assign_org_role = Viewer
189 |
190 | # Background text for the user field on the login page
191 | ;login_hint = email or username
192 |
193 | # Default UI theme ("dark" or "light")
194 | ;default_theme = dark
195 |
196 | # External user management, these options affect the organization users view
197 | ;external_manage_link_url =
198 | ;external_manage_link_name =
199 | ;external_manage_info =
200 |
201 | [auth]
202 | # Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
203 | ;disable_login_form = false
204 |
205 | # Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
206 | ;disable_signout_menu = false
207 |
208 | #################################### Anonymous Auth ##########################
209 | [auth.anonymous]
210 | # enable anonymous access
211 | ;enabled = false
212 |
213 | # specify organization name that should be used for unauthenticated users
214 | ;org_name = Main Org.
215 |
216 | # specify role for unauthenticated users
217 | ;org_role = Viewer
218 |
219 | #################################### Github Auth ##########################
220 | [auth.github]
221 | ;enabled = false
222 | ;allow_sign_up = true
223 | ;client_id = some_id
224 | ;client_secret = some_secret
225 | ;scopes = user:email,read:org
226 | ;auth_url = https://github.com/login/oauth/authorize
227 | ;token_url = https://github.com/login/oauth/access_token
228 | ;api_url = https://api.github.com/user
229 | ;team_ids =
230 | ;allowed_organizations =
231 |
232 | #################################### Google Auth ##########################
233 | [auth.google]
234 | ;enabled = false
235 | ;allow_sign_up = true
236 | ;client_id = some_client_id
237 | ;client_secret = some_client_secret
238 | ;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
239 | ;auth_url = https://accounts.google.com/o/oauth2/auth
240 | ;token_url = https://accounts.google.com/o/oauth2/token
241 | ;api_url = https://www.googleapis.com/oauth2/v1/userinfo
242 | ;allowed_domains =
243 |
244 | #################################### Generic OAuth ##########################
245 | [auth.generic_oauth]
246 | ;enabled = false
247 | ;name = OAuth
248 | ;allow_sign_up = true
249 | ;client_id = some_id
250 | ;client_secret = some_secret
251 | ;scopes = user:email,read:org
252 | ;auth_url = https://foo.bar/login/oauth/authorize
253 | ;token_url = https://foo.bar/login/oauth/access_token
254 | ;api_url = https://foo.bar/user
255 | ;team_ids =
256 | ;allowed_organizations =
257 |
258 | #################################### Grafana.com Auth ####################
259 | [auth.grafana_com]
260 | ;enabled = false
261 | ;allow_sign_up = true
262 | ;client_id = some_id
263 | ;client_secret = some_secret
264 | ;scopes = user:email
265 | ;allowed_organizations =
266 |
267 | #################################### Auth Proxy ##########################
268 | [auth.proxy]
269 | ;enabled = false
270 | ;header_name = X-WEBAUTH-USER
271 | ;header_property = username
272 | ;auto_sign_up = true
273 | ;ldap_sync_ttl = 60
274 | ;whitelist = 192.168.1.1, 192.168.2.1
275 |
276 | #################################### Basic Auth ##########################
277 | [auth.basic]
278 | ;enabled = true
279 |
280 | #################################### Auth LDAP ##########################
281 | [auth.ldap]
282 | ;enabled = false
283 | ;config_file = /etc/grafana/ldap.toml
284 | ;allow_sign_up = true
285 |
286 | #################################### SMTP / Emailing ##########################
287 | [smtp]
288 | ;enabled = false
289 | ;host = localhost:25
290 | ;user =
291 | # If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""
292 | ;password =
293 | ;cert_file =
294 | ;key_file =
295 | ;skip_verify = false
296 | ;from_address = admin@grafana.localhost
297 | ;from_name = Grafana
298 |
299 | [emails]
300 | ;welcome_email_on_sign_up = false
301 |
302 | #################################### Logging ##########################
303 | [log]
304 | # Either "console", "file", "syslog". Default is console and file
305 | # Use space to separate multiple modes, e.g. "console file"
306 | ;mode = console file
307 |
308 | # Either "debug", "info", "warn", "error", "critical", default is "info"
309 | level = debug
310 |
311 | # optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug
312 | ;filters =
313 |
314 |
315 | # For "console" mode only
316 | [log.console]
317 | ;level =
318 |
319 | # log line format, valid options are text, console and json
320 | ;format = console
321 |
322 | # For "file" mode only
323 | [log.file]
324 | ;level =
325 |
326 | # log line format, valid options are text, console and json
327 | ;format = text
328 |
329 | # This enables automated log rotate(switch of following options), default is true
330 | ;log_rotate = true
331 |
332 | # Max line number of single file, default is 1000000
333 | ;max_lines = 1000000
334 |
335 | # Max size shift of single file, default is 28 means 1 << 28, 256MB
336 | ;max_size_shift = 28
337 |
338 | # Segment log daily, default is true
339 | ;daily_rotate = true
340 |
341 | # Expired days of log file(delete after max days), default is 7
342 | ;max_days = 7
343 |
344 | [log.syslog]
345 | ;level =
346 |
347 | # log line format, valid options are text, console and json
348 | ;format = text
349 |
350 | # Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
351 | ;network =
352 | ;address =
353 |
354 | # Syslog facility. user, daemon and local0 through local7 are valid.
355 | ;facility =
356 |
357 | # Syslog tag. By default, the process' argv[0] is used.
358 | ;tag =
359 |
360 |
361 | #################################### AMQP Event Publisher ##########################
362 | [event_publisher]
363 | ;enabled = false
364 | ;rabbitmq_url = amqp://localhost/
365 | ;exchange = grafana_events
366 |
367 | ;#################################### Dashboard JSON files ##########################
368 | [dashboards.json]
369 | ;enabled = false
370 | ;path = /var/lib/grafana/dashboards
371 |
372 | #################################### Alerting ############################
373 | [alerting]
374 | # Disable alerting engine & UI features
375 | ;enabled = true
376 | # Makes it possible to turn off alert rule execution but alerting UI is visible
377 | ;execute_alerts = true
378 |
379 | #################################### Internal Grafana Metrics ##########################
380 | # Metrics available at HTTP API Url /api/metrics
381 | [metrics]
382 | # Disable / Enable internal metrics
383 | ;enabled = true
384 |
385 | # Publish interval
386 | ;interval_seconds = 10
387 |
388 | # Send internal metrics to Graphite
389 | [metrics.graphite]
390 | # Enable by setting the address setting (ex localhost:2003)
391 | ;address =
392 | ;prefix = prod.grafana.%(instance_name)s.
393 |
394 | #################################### Grafana.com integration ##########################
395 | # Url used to to import dashboards directly from Grafana.com
396 | [grafana_com]
397 | ;url = https://grafana.com
398 |
399 | #################################### External image storage ##########################
400 | [external_image_storage]
401 | # Used for uploading images to public servers so they can be included in slack/email messages.
402 | # you can choose between (s3, webdav)
403 | ;provider =
404 |
405 | [external_image_storage.s3]
406 | ;bucket_url =
407 | ;access_key =
408 | ;secret_key =
409 |
410 | [external_image_storage.webdav]
411 | ;url =
412 | ;public_url =
413 | ;username =
414 | ;password =
415 |
--------------------------------------------------------------------------------
/server/mongodb-proxy.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var bodyParser = require('body-parser');
3 | var _ = require('lodash');
4 | var app = express();
5 | const MongoClient = require('mongodb').MongoClient;
6 | const assert = require('assert');
7 | var config = require('config');
8 | var Stopwatch = require("statman-stopwatch");
9 | var moment = require('moment')
10 |
11 | app.use(bodyParser.json());
12 |
13 | // Called by test
14 | app.all('/', function(req, res, next)
15 | {
16 | logRequest(req.body, "/")
17 | setCORSHeaders(res);
18 |
19 | MongoClient.connect(req.body.db.url, function(err, client)
20 | {
21 | if ( err != null )
22 | {
23 | res.send({ status : "error",
24 | display_status : "Error",
25 | message : 'MongoDB Connection Error: ' + err.message });
26 | }
27 | else
28 | {
29 | res.send( { status : "success",
30 | display_status : "Success",
31 | message : 'MongoDB Connection test OK' });
32 | }
33 | next()
34 | })
35 | });
36 |
37 | // Called by template functions and to look up variables
38 | app.all('/search', function(req, res, next)
39 | {
40 | logRequest(req.body, "/search")
41 | setCORSHeaders(res);
42 |
43 | // Generate an id to track requests
44 | const requestId = ++requestIdCounter
45 | // Add state for the queries in this request
46 | var queryStates = []
47 | requestsPending[requestId] = queryStates
48 | // Parse query string in target
49 | queryArgs = parseQuery(req.body.target, {})
50 | if (queryArgs.err != null)
51 | {
52 | queryError(requestId, queryArgs.err, next)
53 | }
54 | else
55 | {
56 | doTemplateQuery(requestId, queryArgs, req.body.db, res, next);
57 | }
58 | });
59 |
60 | // State for queries in flight. As results come it, acts as a semaphore and sends the results back
61 | var requestIdCounter = 0
62 | // Map of request id -> array of results. Results is
63 | // { query, err, output }
64 | var requestsPending = {}
65 |
66 | // Called when a query finishes with an error
67 | function queryError(requestId, err, next)
68 | {
69 | // We only 1 return error per query so it may have been removed from the list
70 | if ( requestId in requestsPending )
71 | {
72 | // Remove request
73 | delete requestsPending[requestId]
74 | // Send back error
75 | next(err)
76 | }
77 | }
78 |
79 | // Called when query finished
80 | function queryFinished(requestId, queryId, results, res, next)
81 | {
82 | // We only 1 return error per query so it may have been removed from the list
83 | if ( requestId in requestsPending )
84 | {
85 | var queryStatus = requestsPending[requestId]
86 | // Mark this as finished
87 | queryStatus[queryId].pending = false
88 | queryStatus[queryId].results = results
89 |
90 | // See if we're all done
91 | var done = true
92 | for ( var i = 0; i < queryStatus.length; i++)
93 | {
94 | if (queryStatus[i].pending == true )
95 | {
96 | done = false
97 | break
98 | }
99 | }
100 |
101 | // If query done, send back results
102 | if (done)
103 | {
104 | // Concatenate results
105 | output = []
106 | for ( var i = 0; i < queryStatus.length; i++)
107 | {
108 | var queryResults = queryStatus[i].results
109 | var keys = Object.keys(queryResults)
110 | for (var k = 0; k < keys.length; k++)
111 | {
112 | var tg = keys[k]
113 | output.push(queryResults[tg])
114 | }
115 | }
116 | res.json(output);
117 | next()
118 | // Remove request
119 | delete requestsPending[requestId]
120 | }
121 | }
122 | }
123 |
124 | // Called to get graph points
125 | app.all('/query', function(req, res, next)
126 | {
127 | logRequest(req.body, "/query")
128 | setCORSHeaders(res);
129 |
130 | // Parse query string in target
131 | substitutions = { "$from" : new Date(req.body.range.from),
132 | "$to" : new Date(req.body.range.to),
133 | "$dateBucketCount" : getBucketCount(req.body.range.from, req.body.range.to, req.body.intervalMs)
134 | }
135 |
136 | // Generate an id to track requests
137 | const requestId = ++requestIdCounter
138 | // Add state for the queries in this request
139 | var queryStates = []
140 | requestsPending[requestId] = queryStates
141 | var error = false
142 |
143 | for ( var queryId = 0; queryId < req.body.targets.length && !error; queryId++)
144 | {
145 | tg = req.body.targets[queryId]
146 | queryArgs = parseQuery(tg.target, substitutions)
147 | queryArgs.type = tg.type
148 | if (queryArgs.err != null)
149 | {
150 | queryError(requestId, queryArgs.err, next)
151 | error = true
152 | }
153 | else
154 | {
155 | // Add to the state
156 | queryStates.push( { pending : true } )
157 |
158 | // Run the query
159 | runAggregateQuery( requestId, queryId, req.body, queryArgs, res, next)
160 | }
161 | }
162 | }
163 | );
164 |
165 | app.use(function(error, req, res, next)
166 | {
167 | // Any request to this server will get here, and will send an HTTP
168 | // response with the error message
169 | res.status(500).json({ message: error.message });
170 | });
171 |
172 | // Get config from server/default.json
173 | var serverConfig = config.get('server');
174 |
175 | app.listen(serverConfig.port);
176 |
177 | console.log("Server is listening on port " + serverConfig.port);
178 |
179 | function setCORSHeaders(res)
180 | {
181 | res.setHeader("Access-Control-Allow-Origin", "*");
182 | res.setHeader("Access-Control-Allow-Methods", "POST");
183 | res.setHeader("Access-Control-Allow-Headers", "accept, content-type");
184 | }
185 |
186 | function forIn(obj, processFunc)
187 | {
188 | var key;
189 | for (key in obj)
190 | {
191 | var value = obj[key]
192 | processFunc(obj, key, value)
193 | if ( value != null && typeof(value) == "object")
194 | {
195 | forIn(value, processFunc)
196 | }
197 | }
198 | }
199 |
200 | function parseQuery(query, substitutions)
201 | {
202 | doc = {}
203 | queryErrors = []
204 |
205 | query = query.trim()
206 | if (query.substring(0,3) != "db.")
207 | {
208 | queryErrors.push("Query must start with db.")
209 | return null
210 | }
211 |
212 | // Query is of the form db.