Commit 83f7ac18 by Daniel Lee

Merge remote-tracking branch 'upstream/master' into 11053-remove-native-scrollbar

parents 25ec7b5b 93e1546f
...@@ -17,6 +17,7 @@ EXPOSE 389 ...@@ -17,6 +17,7 @@ EXPOSE 389
VOLUME ["/etc/ldap", "/var/lib/ldap"] VOLUME ["/etc/ldap", "/var/lib/ldap"]
COPY modules/ /etc/ldap.dist/modules COPY modules/ /etc/ldap.dist/modules
COPY prepopulate/ /etc/ldap.dist/prepopulate
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
......
...@@ -65,7 +65,7 @@ EOF ...@@ -65,7 +65,7 @@ EOF
fi fi
if [[ -n "$SLAPD_ADDITIONAL_SCHEMAS" ]]; then if [[ -n "$SLAPD_ADDITIONAL_SCHEMAS" ]]; then
IFS=","; declare -a schemas=($SLAPD_ADDITIONAL_SCHEMAS) IFS=","; declare -a schemas=($SLAPD_ADDITIONAL_SCHEMAS); unset IFS
for schema in "${schemas[@]}"; do for schema in "${schemas[@]}"; do
slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/schema/${schema}.ldif" >/dev/null 2>&1 slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/schema/${schema}.ldif" >/dev/null 2>&1
...@@ -73,14 +73,18 @@ EOF ...@@ -73,14 +73,18 @@ EOF
fi fi
if [[ -n "$SLAPD_ADDITIONAL_MODULES" ]]; then if [[ -n "$SLAPD_ADDITIONAL_MODULES" ]]; then
IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES) IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES); unset IFS
for module in "${modules[@]}"; do for module in "${modules[@]}"; do
slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1 slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1
done done
fi fi
chown -R openldap:openldap /etc/ldap/slapd.d/ for file in `ls /etc/ldap/prepopulate/*.ldif`; do
slapadd -F /etc/ldap/slapd.d -l "$file"
done
chown -R openldap:openldap /etc/ldap/slapd.d/ /var/lib/ldap/ /var/run/slapd/
else else
slapd_configs_in_env=`env | grep 'SLAPD_'` slapd_configs_in_env=`env | grep 'SLAPD_'`
......
# Notes on OpenLdap Docker Block
Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database.
The ldif files add three users, `ldapviewer`, `ldapeditor` and `ldapadmin`. Two groups, `admins` and `users`, are added that correspond with the group mappings in the default conf/ldap.toml. `ldapadmin` is a member of `admins` and `ldapeditor` is a member of `users`.
Note that users that are added here need to specify a `memberOf` attribute manually as well as the `member` attribute for the group. The `memberOf` module usually does this automatically (if you add a group in Apache Directory Studio for example) but this does not work in the entrypoint script as it uses the `slapadd` command to add entries before the server has started and before the `memberOf` module is loaded.
After adding ldif files to `prepopulate`:
1. Remove your current docker image: `docker rm docker_openldap_1`
2. Build: `docker-compose build`
3. `docker-compose up`
dn: cn=ldapadmin,dc=grafana,dc=org
mail: ldapadmin@grafana.com
userPassword: grafana
objectClass: person
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
sn: ldapadmin
cn: ldapadmin
memberOf: cn=admins,dc=grafana,dc=org
dn: cn=admins,dc=grafana,dc=org
cn: admins
member: cn=ldapadmin,dc=grafana,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=ldapeditor,dc=grafana,dc=org
mail: ldapeditor@grafana.com
userPassword: grafana
objectClass: person
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
sn: ldapeditor
cn: ldapeditor
memberOf: cn=users,dc=grafana,dc=org
dn: cn=users,dc=grafana,dc=org
cn: users
member: cn=ldapeditor,dc=grafana,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=ldapviewer,dc=grafana,dc=org
mail: ldapviewer@grafana.com
userPassword: grafana
objectClass: person
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
sn: ldapviewer
cn: ldapviewer
...@@ -180,14 +180,14 @@ Content-Type: application/json ...@@ -180,14 +180,14 @@ Content-Type: application/json
## Delete Annotation By Id ## Delete Annotation By Id
`DELETE /api/annotation/:id` `DELETE /api/annotations/:id`
Deletes the annotation that matches the specified id. Deletes the annotation that matches the specified id.
**Example Request**: **Example Request**:
```http ```http
DELETE /api/annotation/1 HTTP/1.1 DELETE /api/annotations/1 HTTP/1.1
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
...@@ -204,14 +204,14 @@ Content-Type: application/json ...@@ -204,14 +204,14 @@ Content-Type: application/json
## Delete Annotation By RegionId ## Delete Annotation By RegionId
`DELETE /api/annotation/region/:id` `DELETE /api/annotations/region/:id`
Deletes the annotation that matches the specified region id. A region is an annotation that covers a timerange and has a start and end time. In the Grafana database, this is a stored as two annotations connected by a region id. Deletes the annotation that matches the specified region id. A region is an annotation that covers a timerange and has a start and end time. In the Grafana database, this is a stored as two annotations connected by a region id.
**Example Request**: **Example Request**:
```http ```http
DELETE /api/annotation/region/1 HTTP/1.1 DELETE /api/annotations/region/1 HTTP/1.1
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
......
...@@ -34,7 +34,7 @@ sudo dpkg -i grafana_5.0.4_amd64.deb ...@@ -34,7 +34,7 @@ sudo dpkg -i grafana_5.0.4_amd64.deb
Add the following line to your `/etc/apt/sources.list` file. Add the following line to your `/etc/apt/sources.list` file.
```bash ```bash
deb https://packagecloud.io/grafana/stable/debian/ jessie main deb https://packagecloud.io/grafana/stable/debian/ stretch main
``` ```
Use the above line even if you are on Ubuntu or another Debian version. Use the above line even if you are on Ubuntu or another Debian version.
...@@ -42,7 +42,7 @@ There is also a testing repository if you want beta or release ...@@ -42,7 +42,7 @@ There is also a testing repository if you want beta or release
candidates. candidates.
```bash ```bash
deb https://packagecloud.io/grafana/testing/debian/ jessie main deb https://packagecloud.io/grafana/testing/debian/ stretch main
``` ```
Then add the [Package Cloud](https://packagecloud.io/grafana) key. This Then add the [Package Cloud](https://packagecloud.io/grafana) key. This
......
...@@ -72,7 +72,10 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { ...@@ -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) 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{ cmd := &m.SendWebhookSync{
Url: this.Url, Url: this.Url,
......
define([ import _ from 'lodash';
'lodash', import coreModule from '../core_module';
'jquery',
'../core_module',
],
function (_, $, coreModule) {
'use strict';
coreModule.default.directive('dashClass', function() { /** @ngInject */
return { export function dashClass() {
link: function($scope, elem) { return {
link: function($scope, elem) {
$scope.onAppEvent('panel-fullscreen-enter', function() {
elem.toggleClass('panel-in-fullscreen', true);
});
$scope.onAppEvent('panel-fullscreen-enter', function() { $scope.onAppEvent('panel-fullscreen-exit', function() {
elem.toggleClass('panel-in-fullscreen', true); elem.toggleClass('panel-in-fullscreen', false);
}); });
$scope.onAppEvent('panel-fullscreen-exit', function() { $scope.$watch('ctrl.dashboardViewState.state.editview', function(newValue) {
elem.toggleClass('panel-in-fullscreen', false); if (newValue) {
}); elem.toggleClass('dashboard-page--settings-opening', _.isString(newValue));
setTimeout(function() {
elem.toggleClass('dashboard-page--settings-open', _.isString(newValue));
}, 10);
} else {
elem.removeClass('dashboard-page--settings-opening');
elem.removeClass('dashboard-page--settings-open');
}
});
},
};
}
$scope.$watch('ctrl.dashboardViewState.state.editview', function(newValue) { coreModule.directive('dashClass', dashClass);
if (newValue) {
elem.toggleClass('dashboard-page--settings-opening', _.isString(newValue));
setTimeout(function() {
elem.toggleClass('dashboard-page--settings-open', _.isString(newValue));
}, 10);
} else {
elem.removeClass('dashboard-page--settings-opening');
elem.removeClass('dashboard-page--settings-open');
}
});
}
};
});
});
define([ import _ from 'lodash';
'angular', import coreModule from '../core_module';
'lodash',
'../core_module',
],
function (angular, _, coreModule) {
'use strict';
coreModule.default.service('uiSegmentSrv', function($sce, templateSrv) {
var self = this;
function MetricSegment(options) {
if (options === '*' || options.value === '*') {
this.value = '*';
this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
this.type = options.type;
this.expandable = true;
return;
}
if (_.isString(options)) {
this.value = options;
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
return;
}
// temp hack to work around legacy inconsistency in segment model /** @ngInject */
this.text = options.value; export function uiSegmentSrv($sce, templateSrv) {
let self = this;
this.cssClass = options.cssClass; function MetricSegment(options) {
this.custom = options.custom; if (options === '*' || options.value === '*') {
this.type = options.type; this.value = '*';
this.fake = options.fake; this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
this.value = options.value;
this.selectMode = options.selectMode;
this.type = options.type; this.type = options.type;
this.expandable = options.expandable; this.expandable = true;
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value)); return;
} }
this.getSegmentForValue = function(value, fallbackText) { if (_.isString(options)) {
if (value) { this.value = options;
return this.newSegment(value); this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
} else { return;
return this.newSegment({value: fallbackText, fake: true}); }
}
};
this.newSelectMeasurement = function() {
return new MetricSegment({value: 'select measurement', fake: true});
};
this.newFake = function(text, type, cssClass) {
return new MetricSegment({value: text, fake: true, type: type, cssClass: cssClass});
};
this.newSegment = function(options) {
return new MetricSegment(options);
};
this.newKey = function(key) {
return new MetricSegment({value: key, type: 'key', cssClass: 'query-segment-key' });
};
this.newKeyValue = function(value) {
return new MetricSegment({value: value, type: 'value', cssClass: 'query-segment-value' });
};
this.newCondition = function(condition) {
return new MetricSegment({value: condition, type: 'condition', cssClass: 'query-keyword' });
};
this.newOperator = function(op) {
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
};
this.newOperators = function(ops) { // temp hack to work around legacy inconsistency in segment model
return _.map(ops, function(op) { this.text = options.value;
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
this.cssClass = options.cssClass;
this.custom = options.custom;
this.type = options.type;
this.fake = options.fake;
this.value = options.value;
this.selectMode = options.selectMode;
this.type = options.type;
this.expandable = options.expandable;
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
}
this.getSegmentForValue = function(value, fallbackText) {
if (value) {
return this.newSegment(value);
} else {
return this.newSegment({ value: fallbackText, fake: true });
}
};
this.newSelectMeasurement = function() {
return new MetricSegment({ value: 'select measurement', fake: true });
};
this.newFake = function(text, type, cssClass) {
return new MetricSegment({ value: text, fake: true, type: type, cssClass: cssClass });
};
this.newSegment = function(options) {
return new MetricSegment(options);
};
this.newKey = function(key) {
return new MetricSegment({ value: key, type: 'key', cssClass: 'query-segment-key' });
};
this.newKeyValue = function(value) {
return new MetricSegment({ value: value, type: 'value', cssClass: 'query-segment-value' });
};
this.newCondition = function(condition) {
return new MetricSegment({ value: condition, type: 'condition', cssClass: 'query-keyword' });
};
this.newOperator = function(op) {
return new MetricSegment({ value: op, type: 'operator', cssClass: 'query-segment-operator' });
};
this.newOperators = function(ops) {
return _.map(ops, function(op) {
return new MetricSegment({ value: op, type: 'operator', cssClass: 'query-segment-operator' });
});
};
this.transformToSegments = function(addTemplateVars, variableTypeFilter) {
return function(results) {
let segments = _.map(results, function(segment) {
return self.newSegment({ value: segment.text, expandable: segment.expandable });
}); });
};
this.transformToSegments = function(addTemplateVars, variableTypeFilter) { if (addTemplateVars) {
return function(results) { _.each(templateSrv.variables, function(variable) {
var segments = _.map(results, function(segment) { if (variableTypeFilter === void 0 || variableTypeFilter === variable.type) {
return self.newSegment({value: segment.text, expandable: segment.expandable}); segments.unshift(self.newSegment({ type: 'value', value: '$' + variable.name, expandable: true }));
}
}); });
}
if (addTemplateVars) { return segments;
_.each(templateSrv.variables, function(variable) {
if (variableTypeFilter === void 0 || variableTypeFilter === variable.type) {
segments.unshift(self.newSegment({ type: 'value', value: '$' + variable.name, expandable: true }));
}
});
}
return segments;
};
};
this.newSelectMetric = function() {
return new MetricSegment({value: 'select metric', fake: true});
};
this.newPlusButton = function() {
return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button', cssClass: 'query-part' });
}; };
};
});
this.newSelectMetric = function() {
}); return new MetricSegment({ value: 'select metric', fake: true });
};
this.newPlusButton = function() {
return new MetricSegment({
fake: true,
html: '<i class="fa fa-plus "></i>',
type: 'plus-button',
cssClass: 'query-part',
});
};
}
coreModule.service('uiSegmentSrv', uiSegmentSrv);
import angular from 'angular'; import angular from 'angular';
/** @ngInject */
function grafanaRoutes($routeProvider) { function grafanaRoutes($routeProvider) {
$routeProvider $routeProvider
.when('/playlists', { .when('/playlists', {
......
<div class="gf-form-group"> <div class="gf-form-group">
<h3 class="page-heading">HTTP</h3> <h3 class="page-heading">HTTP</h3>
<div class="gf-form-group"> <div class="gf-form-group">
...@@ -13,12 +11,12 @@ ...@@ -13,12 +11,12 @@
<info-popover mode="right-absolute"> <info-popover mode="right-absolute">
<p>Specify a complete HTTP URL (for example http://your_server:8080)</p> <p>Specify a complete HTTP URL (for example http://your_server:8080)</p>
<span ng-show="current.access === 'direct'"> <span ng-show="current.access === 'direct'">
Your access method is <em>Direct</em>, this means the URL Your access method is <em>Browser</em>, this means the URL
needs to be accessible from the browser. needs to be accessible from the browser.
</span> </span>
<span ng-show="current.access === 'proxy'"> <span ng-show="current.access === 'proxy'">
Your access method is currently <em>Proxy</em>, this means the URL Your access method is <em>Server</em>, this means the URL
needs to be accessible from the grafana backend. needs to be accessible from the grafana backend/server.
</span> </span>
</info-popover> </info-popover>
</div> </div>
...@@ -27,14 +25,38 @@ ...@@ -27,14 +25,38 @@
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
<span class="gf-form-label width-7">Access</span> <span class="gf-form-label width-7">Access</span>
<div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24"> <div class="gf-form-select-wrapper max-width-24">
<select class="gf-form-input" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select> <select class="gf-form-input" ng-model="current.access" ng-options="f.key as f.value for f in [{key: 'proxy', value: 'Server (Default)'}, { key: 'direct', value: 'Browser'}]"></select>
<info-popover mode="right-absolute">
Direct = URL is used directly from browser<br>
Proxy = Grafana backend will proxy the request
</info-popover>
</div> </div>
</div> </div>
<div class="gf-form">
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showAccessHelp = !ctrl.showAccessHelp">
Help&nbsp;
<i class="fa fa-caret-down" ng-show="ctrl.showAccessHelp"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showAccessHelp">&nbsp;</i>
</label>
</div>
</div>
<div class="alert alert-info" ng-show="ctrl.showAccessHelp">
<div class="alert-body">
<p>
Access mode controls how requests to the data source will be handled.
<strong><i>Server</i></strong> should be the preferred way if nothing else stated.
</p>
<div class="alert-title">Server access mode (Default):</div>
<p>
All requests will be made from the browser to Grafana backend/server which in turn will forward the requests to the data source
and by that circumvent possible Cross-Origin Resource Sharing (CORS) requirements.
The URL needs to be accessible from the grafana backend/server if you select this access mode.
</p>
<div class="alert-title">Browser access mode:</div>
<p>
All requests will be made from the browser directly to the data source and may be subject to
Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this
access mode.
</p>
</div>
</div> </div>
</div> </div>
...@@ -135,4 +157,3 @@ ...@@ -135,4 +157,3 @@
</div> </div>
</div> </div>
</div> </div>
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
data-min-length=0 data-items=1000 ng-model-onblur ng-change="ctrl.refreshMetricData()"> data-min-length=0 data-items=1000 ng-model-onblur ng-change="ctrl.refreshMetricData()">
</input> </input>
<info-popover mode="right-absolute"> <info-popover mode="right-absolute">
Controls the name of the time series, using name or pattern. For example {{hostname}} will be replaced with label value for Controls the name of the time series, using name or pattern. For example <span ng-non-bindable>{{hostname}}</span> will be replaced with label value for
the label hostname. the label hostname.
</info-popover> </info-popover>
</div> </div>
......
.gicon { .gicon {
line-height: 1; line-height: 1;
display: inline-block; display: inline-block;
//width: 1.1057142857em; width: 1.1057142857em;
//height: 1.1057142857em; height: 1.1057142857em;
height: 22px;
width: 22px;
text-align: center; text-align: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
......
...@@ -123,6 +123,8 @@ ...@@ -123,6 +123,8 @@
position: relative; position: relative;
opacity: 0.7; opacity: 0.7;
font-size: 130%; font-size: 130%;
height: 22px;
width: 22px;
} }
.fa { .fa {
......
...@@ -33,7 +33,7 @@ div.flot-text { ...@@ -33,7 +33,7 @@ div.flot-text {
border: $panel-border; border: $panel-border;
position: relative; position: relative;
border-radius: 3px; border-radius: 3px;
//height: 100%; height: 100%;
&.panel-transparent { &.panel-transparent {
background-color: transparent; background-color: transparent;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment