Merge remote-tracking branch 'upstream/master' into postgres-query-builder

This commit is contained in:
Sven Klemm 2018-04-13 22:18:32 +02:00
commit 1c0a188272
39 changed files with 653 additions and 127 deletions

View File

@ -28,7 +28,8 @@
* **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny)
* **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189)
* **Prometheus**: Escape backslash in labels correctly. [#10555](https://github.com/grafana/grafana/issues/10555), thx [@roidelapluie](https://github.com/roidelapluie)
* **Variables** Case-insensitive sorting for template values [#11128](https://github.com/grafana/grafana/issues/11128) thx [@cross](https://github.com/cross)
* **Variables**: Case-insensitive sorting for template values [#11128](https://github.com/grafana/grafana/issues/11128) thx [@cross](https://github.com/cross)
* **Annotations (native)**: Change default limit from 10 to 100 when querying api [#11569](https://github.com/grafana/grafana/issues/11569), thx [@flopp999](https://github.com/flopp999)
# 5.0.4 (2018-03-28)

View File

@ -7,7 +7,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana
environment:
nodejs_version: "6"
GOPATH: c:\gopath
GOVERSION: 1.9.2
GOVERSION: 1.10
install:
- rmdir c:\go /s /q

View File

@ -138,6 +138,7 @@ datasources:
```
#### Custom Settings per Datasource
Please refer to each datasource documentation for specific provisioning examples.
| Datasource | Misc |
| ---- | ---- |

View File

@ -43,6 +43,40 @@ server is running on AWS you can use IAM Roles and authentication will be handle
Checkout AWS docs on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
## IAM Policies
Grafana needs permissions granted via IAM to be able to read CloudWatch metrics
and EC2 tags/instances. You can attach these permissions to IAM roles and
utilize Grafana's built-in support for assuming roles.
Here is a minimal policy example:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadingMetricsFromCloudWatch",
"Effect": "Allow",
"Action": [
"cloudwatch:ListMetrics",
"cloudwatch:GetMetricStatistics"
],
"Resource": "*"
},
{
"Sid": "AllowReadingTagsFromEC2",
"Effect": "Allow",
"Action": [
"ec2:DescribeTags",
"ec2:DescribeInstances"
],
"Resource": "*"
}
]
}
```
### AWS credentials file
Create a file at `~/.aws/credentials`. That is the `HOME` path for user running grafana-server.
@ -173,3 +207,37 @@ Amazon provides 1 million CloudWatch API requests each month at no additional ch
it costs $0.01 per 1,000 GetMetricStatistics or ListMetrics requests. For each query Grafana will
issue a GetMetricStatistics request and every time you pick a dimension in the query editor
Grafana will issue a ListMetrics request.
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
Using a credentials file
```yaml
apiVersion: 1
datasources:
- name: Cloudwatch
type: cloudwatch
jsonData:
authType: credentials
defaultRegion: eu-west-2
```
Using `accessKey` and `secretKey`
```yaml
apiVersion: 1
datasources:
- name: Cloudwatch
type: cloudwatch
jsonData:
authType: keys
defaultRegion: eu-west-2
secureJsonData:
accessKey: "<your access key>"
secretKey: "<your secret key>"
```

View File

@ -137,3 +137,23 @@ Query | You can leave the search query blank or specify a lucene query
Time | The name of the time field, needs to be date field.
Text | Event description field.
Tags | Optional field name to use for event tags (can be an array or a CSV string).
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: Elastic
type: elasticsearch
access: proxy
database: "[metrics-]YYYY.MM.DD"
url: http://localhost:9200
jsonData:
interval: Daily
timeField: "@timestamp"
```

View File

@ -120,3 +120,21 @@ queries via the Dashboard menu / Annotations view.
Graphite supports two ways to query annotations. A regular metric query, for this you use the `Graphite query` textbox. A Graphite events query, use the `Graphite event tags` textbox,
specify a tag or wildcard (leave empty should also work)
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: Graphite
type: graphite
access: proxy
url: http://localhost:8080
jsonData:
graphiteVersion: "1.1"
```

View File

@ -174,3 +174,22 @@ SELECT title, description from events WHERE $timeFilter order asc
For InfluxDB you need to enter a query like in the above example. You need to have the ```where $timeFilter```
part. If you only select one column you will not need to enter anything in the column mapping fields. The
Tags field can be a comma separated string.
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: InfluxDB
type: influxdb
access: proxy
database: site
user: grafana
password: grafana
url: http://localhost:8086
```

View File

@ -225,3 +225,21 @@ tags | Optional field name to use for event tags as a comma separated string.
## Alerting
Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule conditions.
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: MySQL
type: mysql
url: localhost:3306
database: grafana
user: grafana
password: password
```

View File

@ -88,3 +88,22 @@ Query | Description
*tag_values(cpu, hostanme, env=$env, region=$region)* | Return tag values for cpu metric, selected env tag value, selected region tag value and tag key hostname
For details on OpenTSDB metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html)
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: OpenTsdb
type: opentsdb
access: proxy
url: http://localhost:4242
jsonData:
tsdbResolution: 1
tsdbVersion: 1
```

View File

@ -217,3 +217,25 @@ tags | Optional field name to use for event tags as a comma separated string.
Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule
conditions.
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: Postgres
type: postgres
url: localhost:5432
database: grafana
user: grafana
secureJsonData:
password: "Password!"
jsonData:
sslmode: "disable" # disable/require/verify-ca/verify-full
```

View File

@ -34,7 +34,7 @@ Name | Description
*Basic Auth* | Enable basic authentication to the Prometheus data source.
*User* | Name of your Prometheus user
*Password* | Database user's password
*Scrape interval* | This will be used as a lower limit for the Prometheus step query parameter. Default value is 15s.
*Scrape interval* | This will be used as a lower limit for the Prometheus step query parameter. Default value is 15s.
## Query editor
@ -100,3 +100,19 @@ The step option is useful to limit the number of events returned from your query
## Getting Grafana metrics into Prometheus
Since 4.6.0 Grafana exposes metrics for Prometheus on the `/metrics` endpoint. We also bundle a dashboard within Grafana so you can get started viewing your metrics faster. You can import the bundled dashboard by going to the data source edit page and click the dashboard tab. There you can find a dashboard for Grafana and one for Prometheus. Import and start viewing all the metrics!
## Configure datasource with provisioning
It's now possible to configure datasources using config files with Grafanas provisioning system. You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here are some provisioning examples for this datasource.
```yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
```

View File

@ -136,6 +136,7 @@
"angular-route": "^1.6.6",
"angular-sanitize": "^1.6.6",
"babel-polyfill": "^6.26.0",
"baron": "^3.0.3",
"brace": "^0.10.0",
"classnames": "^2.2.5",
"clipboard": "^1.7.1",
@ -151,7 +152,6 @@
"moment": "^2.18.1",
"mousetrap": "^1.6.0",
"mousetrap-global-bind": "^1.1.0",
"perfect-scrollbar": "^1.2.0",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",

View File

@ -189,12 +189,6 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
}
func (proxy *DataSourceProxy) validateRequest() error {
if proxy.ds.Type == m.DS_INFLUXDB {
if proxy.ctx.Query("db") != proxy.ds.Database {
return errors.New("Datasource is not configured to allow this database")
}
}
if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) {
return errors.New("Target url is not a valid target")
}

View File

@ -72,7 +72,10 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Error("Failed to create Json data", "error", err, "dingding", this.Name)
}
body, _ := bodyJSON.MarshalJSON()
body, err := bodyJSON.MarshalJSON()
if err != nil {
return err
}
cmd := &m.SendWebhookSync{
Url: this.Url,

View File

@ -202,7 +202,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
}
if query.Limit == 0 {
query.Limit = 10
query.Limit = 100
}
sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit))

View File

@ -1,5 +1,5 @@
import React from 'react';
import PerfectScrollbar from 'perfect-scrollbar';
import baron from 'baron';
export interface Props {
children: any;
@ -8,31 +8,36 @@ export interface Props {
export default class ScrollBar extends React.Component<Props, any> {
private container: any;
private ps: PerfectScrollbar;
private scrollbar: baron;
constructor(props) {
super(props);
}
componentDidMount() {
this.ps = new PerfectScrollbar(this.container, {
wheelPropagation: true,
this.scrollbar = baron({
root: this.container.parentElement,
scroller: this.container,
bar: '.baron__bar',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
track: '.baron__track',
});
}
componentDidUpdate() {
this.ps.update();
this.scrollbar.update();
}
componentWillUnmount() {
this.ps.destroy();
this.scrollbar.dispose();
}
// methods can be invoked by outside
setScrollTop(top) {
if (this.container) {
this.container.scrollTop = top;
this.ps.update();
this.scrollbar.update();
return true;
}
@ -42,7 +47,7 @@ export default class ScrollBar extends React.Component<Props, any> {
setScrollLeft(left) {
if (this.container) {
this.container.scrollLeft = left;
this.ps.update();
this.scrollbar.update();
return true;
}
@ -55,8 +60,14 @@ export default class ScrollBar extends React.Component<Props, any> {
render() {
return (
<div className={this.props.className} ref={this.handleRef}>
{this.props.children}
<div className="baron baron__root baron__clipper">
<div className={this.props.className + ' baron__scroller'} ref={this.handleRef}>
{this.props.children}
</div>
<div className="baron__track">
<div className="baron__bar" />
</div>
</div>
);
}

View File

@ -167,6 +167,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
if (sidemenuHidden) {
sidemenuHidden = false;
body.addClass('sidemenu-open');
appEvents.emit('toggle-inactive-mode');
$timeout(function() {
$rootScope.$broadcast('render');
}, 100);

View File

@ -0,0 +1,41 @@
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
export function pageScrollbar() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
let lastPos = 0;
appEvents.on(
'dash-scroll',
evt => {
if (evt.restore) {
elem[0].scrollTop = lastPos;
return;
}
lastPos = elem[0].scrollTop;
if (evt.animate) {
elem.animate({ scrollTop: evt.pos }, 500);
} else {
elem[0].scrollTop = evt.pos;
}
},
scope
);
scope.$on('$routeChangeSuccess', () => {
lastPos = 0;
elem[0].scrollTop = 0;
elem[0].focus();
});
elem[0].tabIndex = -1;
elem[0].focus();
},
};
}
coreModule.directive('pageScrollbar', pageScrollbar);

View File

@ -1,15 +1,44 @@
import PerfectScrollbar from 'perfect-scrollbar';
import $ from 'jquery';
import baron from 'baron';
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
const scrollBarHTML = `
<div class="baron__track">
<div class="baron__bar"></div>
</div>
`;
const scrollRootClass = 'baron baron__root';
const scrollerClass = 'baron__scroller';
export function geminiScrollbar() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
let scrollbar = new PerfectScrollbar(elem[0], {
wheelPropagation: true,
wheelSpeed: 3,
});
let scrollRoot = elem.parent();
let scroller = elem;
if (attrs.grafanaScrollbar && attrs.grafanaScrollbar === 'scrollonroot') {
scrollRoot = scroller;
}
scrollRoot.addClass(scrollRootClass);
$(scrollBarHTML).appendTo(scrollRoot);
elem.addClass(scrollerClass);
let scrollParams = {
root: scrollRoot[0],
scroller: scroller[0],
bar: '.baron__bar',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
track: '.baron__track',
direction: 'v',
};
let scrollbar = baron(scrollParams);
let lastPos = 0;
appEvents.on(
@ -31,13 +60,24 @@ export function geminiScrollbar() {
scope
);
// force updating dashboard width
appEvents.on('toggle-sidemenu', forceUpdate, scope);
appEvents.on('toggle-sidemenu-hidden', forceUpdate, scope);
appEvents.on('toggle-view-mode', forceUpdate, scope);
appEvents.on('toggle-kiosk-mode', forceUpdate, scope);
appEvents.on('toggle-inactive-mode', forceUpdate, scope);
function forceUpdate() {
scrollbar.scroll();
}
scope.$on('$routeChangeSuccess', () => {
lastPos = 0;
elem[0].scrollTop = 0;
});
scope.$on('$destroy', () => {
scrollbar.destroy();
scrollbar.dispose();
});
},
};

View File

@ -19,6 +19,7 @@
<div class="search-dropdown">
<div class="search-dropdown__col_1">
<div class="search-results-scroller">
<div class="search-results-container" grafana-scrollbar>
<h6 ng-show="!ctrl.isLoading && ctrl.results.length === 0">No dashboards matching your query were found.</h6>
<dashboard-search-results
@ -27,6 +28,7 @@
on-folder-expanding="ctrl.folderExpanding()"
on-folder-expanded="ctrl.folderExpanded($folder)" />
</div>
</div>
</div>
<div class="search-dropdown__col_2">

View File

@ -47,6 +47,7 @@ import { NavModelSrv, NavModel } from './nav_model_srv';
import { userPicker } from './components/user_picker';
import { teamPicker } from './components/team_picker';
import { geminiScrollbar } from './components/scroll/scroll';
import { pageScrollbar } from './components/scroll/page_scroll';
import { gfPageDirective } from './components/gf_page';
import { orgSwitcher } from './components/org_switcher';
import { profiler } from './profiler';
@ -85,6 +86,7 @@ export {
userPicker,
teamPicker,
geminiScrollbar,
pageScrollbar,
gfPageDirective,
orgSwitcher,
manageDashboardsDirective,

View File

@ -103,7 +103,7 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
render() {
return (
<div className="panel-container">
<div className="panel-container add-panel-container">
<div className="add-panel">
<div className="add-panel__header">
<i className="gicon gicon-add-panel" />

View File

@ -196,9 +196,10 @@ export class DashboardViewState {
this.oldTimeRange = ctrl.range;
this.fullscreenPanel = panelScope;
// Firefox doesn't return scrollTop postion properly if 'dash-scroll' is emitted after setViewMode()
this.$scope.appEvent('dash-scroll', { animate: false, pos: 0 });
this.dashboard.setViewMode(ctrl.panel, true, ctrl.editMode);
this.$scope.appEvent('panel-fullscreen-enter', { panelId: ctrl.panel.id });
this.$scope.appEvent('dash-scroll', { animate: false, pos: 0 });
}
registerPanel(panelScope) {

View File

@ -1,6 +1,7 @@
import angular from 'angular';
import $ from 'jquery';
import Drop from 'tether-drop';
import PerfectScrollbar from 'perfect-scrollbar';
import baron from 'baron';
var module = angular.module('grafana.directives');
@ -86,6 +87,9 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
function panelHeightUpdated() {
panelContent.css({ height: ctrl.height + 'px' });
}
function resizeScrollableContent() {
if (panelScrollbar) {
panelScrollbar.update();
}
@ -100,9 +104,30 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
// update scrollbar after mounting
ctrl.events.on('component-did-mount', () => {
if (ctrl.__proto__.constructor.scrollable) {
panelScrollbar = new PerfectScrollbar(panelContent[0], {
wheelPropagation: true,
const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable';
const scrollerClass = 'baron__scroller';
const scrollBarHTML = `
<div class="baron__track">
<div class="baron__bar"></div>
</div>
`;
let scrollRoot = panelContent;
let scroller = panelContent.find(':first').find(':first');
scrollRoot.addClass(scrollRootClass);
$(scrollBarHTML).appendTo(scrollRoot);
scroller.addClass(scrollerClass);
panelScrollbar = baron({
root: scrollRoot[0],
scroller: scroller[0],
bar: '.baron__bar',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
});
panelScrollbar.scroll();
}
});
@ -110,6 +135,7 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
ctrl.calculatePanelHeight();
panelHeightUpdated();
$timeout(() => {
resizeScrollableContent();
ctrl.render();
});
});
@ -199,7 +225,7 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
}
if (panelScrollbar) {
panelScrollbar.update();
panelScrollbar.dispose();
}
});
},

View File

@ -198,7 +198,9 @@ export class QueryVariable implements Variable {
}
});
} else if (sortType === 3) {
options = _.sortBy(options, opt => { return _.toLower(opt.text); });
options = _.sortBy(options, opt => {
return _.toLower(opt.text);
});
}
if (reverseSort) {

View File

@ -1,18 +1,18 @@
<div dash-class ng-if="ctrl.dashboard">
<dashnav dashboard="ctrl.dashboard"></dashnav>
<div class="scroll-canvas scroll-canvas--dashboard" grafana-scrollbar>
<dashboard-settings dashboard="ctrl.dashboard"
ng-if="ctrl.dashboardViewState.state.editview"
class="dashboard-settings">
</dashboard-settings>
<div class="scroll-canvas scroll-canvas--dashboard" page-scrollbar>
<dashboard-settings dashboard="ctrl.dashboard"
ng-if="ctrl.dashboardViewState.state.editview"
class="dashboard-settings">
</dashboard-settings>
<div class="dashboard-container">
<dashboard-submenu ng-if="ctrl.dashboard.meta.submenuEnabled" dashboard="ctrl.dashboard">
</dashboard-submenu>
<div class="dashboard-container">
<dashboard-submenu ng-if="ctrl.dashboard.meta.submenuEnabled" dashboard="ctrl.dashboard">
</dashboard-submenu>
<dashboard-grid get-panel-container="ctrl.getPanelContainer">
</dashboard-grid>
</div>
</div>
<dashboard-grid get-panel-container="ctrl.getPanelContainer">
</dashboard-grid>
</div>
</div>
</div>

View File

@ -82,7 +82,7 @@ export default class InfluxDatasource {
// replace templated variables
allQueries = this.templateSrv.replace(allQueries, scopedVars);
return this._seriesQuery(allQueries).then((data): any => {
return this._seriesQuery(allQueries, options).then((data): any => {
if (!data || !data.results) {
return [];
}
@ -135,7 +135,7 @@ export default class InfluxDatasource {
var query = options.annotation.query.replace('$timeFilter', timeFilter);
query = this.templateSrv.replace(query, null, 'regex');
return this._seriesQuery(query).then(data => {
return this._seriesQuery(query, options).then(data => {
if (!data || !data.results || !data.results[0]) {
throw { message: 'No results in response from InfluxDB' };
}
@ -164,30 +164,30 @@ export default class InfluxDatasource {
return false;
}
metricFindQuery(query) {
metricFindQuery(query: string, options?: any) {
var interpolated = this.templateSrv.replace(query, null, 'regex');
return this._seriesQuery(interpolated).then(_.curry(this.responseParser.parse)(query));
return this._seriesQuery(interpolated, options).then(_.curry(this.responseParser.parse)(query));
}
getTagKeys(options) {
var queryBuilder = new InfluxQueryBuilder({ measurement: '', tags: [] }, this.database);
var query = queryBuilder.buildExploreQuery('TAG_KEYS');
return this.metricFindQuery(query);
return this.metricFindQuery(query, options);
}
getTagValues(options) {
var queryBuilder = new InfluxQueryBuilder({ measurement: '', tags: [] }, this.database);
var query = queryBuilder.buildExploreQuery('TAG_VALUES', options.key);
return this.metricFindQuery(query);
return this.metricFindQuery(query, options);
}
_seriesQuery(query) {
_seriesQuery(query: string, options?: any) {
if (!query) {
return this.$q.when({ results: [] });
}
return this._influxRequest('GET', '/query', { q: query, epoch: 'ms' });
return this._influxRequest('GET', '/query', { q: query, epoch: 'ms' }, options);
}
serializeParams(params) {
@ -225,21 +225,21 @@ export default class InfluxDatasource {
});
}
_influxRequest(method, url, data) {
var self = this;
_influxRequest(method: string, url: string, data: any, options?: any) {
const currentUrl = this.urls.shift();
this.urls.push(currentUrl);
var currentUrl = self.urls.shift();
self.urls.push(currentUrl);
let params: any = {};
var params: any = {};
if (self.username) {
params.u = self.username;
params.p = self.password;
if (this.username) {
params.u = this.username;
params.p = this.password;
}
if (self.database) {
params.db = self.database;
if (options && options.database) {
params.db = options.database;
} else if (this.database) {
params.db = this.database;
}
if (method === 'GET') {
@ -247,7 +247,7 @@ export default class InfluxDatasource {
data = null;
}
var options: any = {
let req: any = {
method: method,
url: currentUrl + url,
params: params,
@ -257,15 +257,15 @@ export default class InfluxDatasource {
paramSerializer: this.serializeParams,
};
options.headers = options.headers || {};
req.headers = req.headers || {};
if (this.basicAuth || this.withCredentials) {
options.withCredentials = true;
req.withCredentials = true;
}
if (self.basicAuth) {
options.headers.Authorization = self.basicAuth;
if (this.basicAuth) {
req.headers.Authorization = this.basicAuth;
}
return this.backendSrv.datasourceRequest(options).then(
return this.backendSrv.datasourceRequest(req).then(
result => {
return result.data;
},

View File

@ -23,6 +23,20 @@
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>Database Access</h5>
<p>
Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows
switching the database in the query. For example:
<code>SHOW MEASUREMENTS ON _internal</code> or <code>SELECT * FROM "_internal".."database" LIMIT 10</code>
<br/><br/>
To support data isolation and security, make sure appropriate permissions are configured in InfluxDB.
</p>
</div>
</div>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">

View File

@ -1,17 +1,19 @@
<div class="dashlist" ng-repeat="group in ctrl.groups">
<div class="dashlist-section" ng-if="group.show">
<h6 class="dashlist-section-header" ng-show="ctrl.panel.headings">
{{group.header}}
</h6>
<div class="dashlist-item" ng-repeat="dash in group.list">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="{{dash.url}}">
<span class="dashlist-title">
{{dash.title}}
</span>
<span class="dashlist-star" ng-click="ctrl.starDashboard(dash, $event)">
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
</span>
</a>
<div>
<div class="dashlist" ng-repeat="group in ctrl.groups">
<div class="dashlist-section" ng-if="group.show">
<h6 class="dashlist-section-header" ng-show="ctrl.panel.headings">
{{group.header}}
</h6>
<div class="dashlist-item" ng-repeat="dash in group.list">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="{{dash.url}}">
<span class="dashlist-title">
{{dash.title}}
</span>
<span class="dashlist-star" ng-click="ctrl.starDashboard(dash, $event)">
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
</span>
</a>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import PerfectScrollbar from 'perfect-scrollbar';
import baron from 'baron';
var module = angular.module('grafana.directives');
@ -16,11 +16,10 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
var i;
var legendScrollbar;
const legendRightDefaultWidth = 10;
let legendElem = elem.parent();
scope.$on('$destroy', function() {
if (legendScrollbar) {
legendScrollbar.destroy();
}
destroyScrollbar();
});
ctrl.events.on('render-legend', () => {
@ -112,7 +111,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
}
function render() {
let legendWidth = elem.width();
let legendWidth = legendElem.width();
if (!ctrl.panel.legend.show) {
elem.empty();
firstRender = true;
@ -134,8 +133,8 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
// Set width so it works with IE11
var width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : '';
var ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : '';
elem.css('min-width', width);
elem.css('width', ieWidth);
legendElem.css('min-width', width);
legendElem.css('width', ieWidth);
elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
@ -241,8 +240,10 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
tbodyElem.append(tableHeaderElem);
tbodyElem.append(seriesElements);
elem.append(tbodyElem);
tbodyElem.wrap('<div class="graph-legend-scroll"></div>');
} else {
elem.append(seriesElements);
elem.append('<div class="graph-legend-scroll"></div>');
elem.find('.graph-legend-scroll').append(seriesElements);
}
if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
@ -253,23 +254,45 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
}
function addScrollbar() {
const scrollbarOptions = {
// Number of pixels the content height can surpass the container height without enabling the scroll bar.
scrollYMarginOffset: 2,
suppressScrollX: true,
wheelPropagation: true,
const scrollRootClass = 'baron baron__root';
const scrollerClass = 'baron__scroller';
const scrollBarHTML = `
<div class="baron__track">
<div class="baron__bar"></div>
</div>
`;
let scrollRoot = elem;
let scroller = elem.find('.graph-legend-scroll');
// clear existing scroll bar track to prevent duplication
scrollRoot.find('.baron__track').remove();
scrollRoot.addClass(scrollRootClass);
$(scrollBarHTML).appendTo(scrollRoot);
scroller.addClass(scrollerClass);
let scrollbarParams = {
root: scrollRoot[0],
scroller: scroller[0],
bar: '.baron__bar',
track: '.baron__track',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
};
if (!legendScrollbar) {
legendScrollbar = new PerfectScrollbar(elem[0], scrollbarOptions);
legendScrollbar = baron(scrollbarParams);
} else {
legendScrollbar.update();
destroyScrollbar();
legendScrollbar = baron(scrollbarParams);
}
legendScrollbar.scroll();
}
function destroyScrollbar() {
if (legendScrollbar) {
legendScrollbar.destroy();
legendScrollbar.dispose();
legendScrollbar = undefined;
}
}

View File

@ -3,7 +3,9 @@ var template = `
<div class="graph-panel__chart" grafana-graph ng-dblclick="ctrl.zoomOut()">
</div>
<div class="graph-legend" graph-legend></div>
<div class="graph-legend">
<div class="graph-legend-content" graph-legend></div>
</div>
</div>
`;

View File

@ -1,5 +1,13 @@
.add-panel-container {
height: 100%;
}
.add-panel {
height: 100%;
.baron__root {
height: calc(100% - 43px);
}
}
.add-panel__header {
@ -39,7 +47,6 @@
flex-direction: row;
flex-wrap: wrap;
overflow: auto;
height: calc(100% - 43px);
align-content: flex-start;
justify-content: space-around;
position: relative;

View File

@ -49,6 +49,7 @@
}
.graph-legend {
display: flex;
flex: 0 1 auto;
max-height: 30%;
margin: 0;
@ -56,11 +57,27 @@
padding-top: 6px;
position: relative;
// fix for Firefox (white stripe on the right of scrollbar)
width: calc(100% - 1px);
.popover-content {
padding: 0;
}
}
.graph-legend-content {
position: relative;
// fix for Firefox (white stripe on the right of scrollbar)
width: calc(100% - 1px);
}
.graph-legend-scroll {
position: relative;
overflow: auto !important;
padding: 1px;
}
.graph-legend-icon {
position: relative;
padding-right: 4px;
@ -115,8 +132,20 @@
// fix for phantomjs
.body--phantomjs {
.graph-panel--legend-right {
.graph-legend {
display: inline-block;
}
.graph-panel__chart {
display: flex;
}
.graph-legend-table {
display: table;
.graph-legend-scroll {
display: table;
}
}
}
}
@ -124,9 +153,9 @@
.graph-legend-table {
tbody {
display: block;
position: relative;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
padding-bottom: 1px;
padding-right: 5px;
padding-left: 5px;

View File

@ -9,6 +9,11 @@
-ms-touch-action: auto;
}
// ._scrollbar {
// overflow-x: hidden !important;
// overflow-y: auto;
// }
/*
* Scrollbar rail styles
*/
@ -101,7 +106,7 @@
opacity: 0.9;
}
// Srollbars
// Scrollbars
//
::-webkit-scrollbar {
@ -172,3 +177,120 @@
border-top: 1px solid $scrollbarBorder;
border-left: 1px solid $scrollbarBorder;
}
// Baron styles
.baron {
// display: inline-block; // this brakes phantomjs rendering (width becomes 0)
overflow: hidden;
}
// Fix for side menu on mobile devices
.main-view.baron {
width: unset;
}
.baron__clipper {
position: relative;
overflow: hidden;
}
.baron__scroller {
overflow-y: scroll;
-ms-overflow-style: none;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
border: 0;
padding: 0;
width: 100%;
height: 100%;
-webkit-overflow-scrolling: touch;
/* remove line to customize scrollbar in iOs */
}
.baron__scroller::-webkit-scrollbar {
width: 0;
height: 0;
}
.baron__track {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
.baron._scrollbar .baron__track {
display: block;
}
.baron__free {
position: absolute;
top: 0;
bottom: 0;
right: 0;
}
.baron__bar {
display: none;
position: absolute;
right: 0;
z-index: 1;
// width: 10px;
background: #999;
// height: 15px;
width: 15px;
transition: background-color 0.2s linear, opacity 0.2s linear;
opacity: 0;
}
.baron._scrollbar .baron__bar {
display: block;
@include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
border-radius: 6px;
width: 6px;
/* there must be 'right' for ps__thumb-y */
right: 0px;
/* please don't change 'position' */
position: absolute;
// background-color: transparent;
// opacity: 0.6;
&:hover,
&:focus {
// background-color: transparent;
opacity: 0.9;
}
}
.panel-hover-highlight .baron__track .baron__bar {
opacity: 0.6;
}
.baron._scrolling > .baron__track .baron__bar {
opacity: 0.9;
}
// fix for phantomjs
.body--phantomjs .baron__track .baron__bar {
opacity: 0 !important;
}
.baron__control {
display: none;
}
.baron.panel-content--scrollable {
// Width needs to be set to prevent content width issues
// Set to less than 100% for fixing Firefox issue (white stripe on the right of scrollbar)
width: calc(100% - 2px);
.baron__scroller {
padding-top: 1px;
}
}

View File

@ -102,14 +102,21 @@
}
}
.search-results-scroller {
display: flex;
position: relative;
}
.search-results-container {
height: 100%;
display: block;
padding: $spacer;
position: relative;
flex-grow: 10;
margin-bottom: 1rem;
// Fix for search scroller in mobile view
height: unset;
.label-tag {
margin-left: 6px;
font-size: 11px;

View File

@ -28,12 +28,20 @@
width: 100%;
overflow: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
&--dashboard {
height: calc(100% - 56px);
}
}
// fix for phantomjs
.body--phantomjs {
.scroll-canvas {
overflow: hidden;
}
}
.page-body {
padding-top: $spacer*2;
min-height: 500px;

View File

@ -16,7 +16,7 @@
<link rel="icon" type="image/png" href="public/img/fav32.png">
<link rel="mask-icon" href="public/img/grafana_mask_icon.svg" color="#F05A28">
<link rel="apple-touch-icon" href="public/img/fav32.png">
</head>
<body ng-cloak class="theme-[[ .Theme ]]">
@ -40,7 +40,7 @@
</div>
<div class="main-view">
<div class="scroll-canvas" grafana-scrollbar>
<div class="scroll-canvas" page-scrollbar>
<div ng-view></div>
<footer class="footer">

View File

@ -20,17 +20,4 @@ echo "building backend with install to cache pkgs"
exit_if_fail time go install ./pkg/cmd/grafana-server
echo "running go test"
set -e
echo "" > coverage.txt
time for d in $(go list ./pkg/...); do
exit_if_fail go test -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done
echo "Publishing go code coverage"
bash <(curl -s https://codecov.io/bash) -cF go
go test ./pkg/...

View File

@ -1162,6 +1162,10 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
baron@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/baron/-/baron-3.0.3.tgz#0f0a08a567062882e130a0ecfd41a46d52103f4a"
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
@ -7503,10 +7507,6 @@ pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
perfect-scrollbar@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.2.0.tgz#ad23a2529c17f4535f21d1486f8bc3046e31a9d2"
performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"