Commit b762f56a by Torkel Ödegaard

Merge branch 'master' into panel_repeat

Conflicts:
	public/app/features/templating/templateValuesSrv.js
parents 8c14e565 74a8fa61
# 2.0.3 (unreleased)
# 2.1.0 (unreleased - master branch)
**Backend**
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
# 2.0.3 (unreleased - 2.0.x branch)
**Fixes**
- [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed
......
......@@ -137,18 +137,20 @@ org_role = Viewer
#################################### Github Auth ##########################
[auth.github]
enabled = false
allow_sign_up = false
client_id = some_id
client_secret = some_secret
scopes = user:email
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
team_ids =
allowed_domains =
allow_sign_up = false
#################################### Google Auth ##########################
[auth.google]
enabled = false
allow_sign_up = false
client_id = some_client_id
client_secret = some_client_secret
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
......@@ -156,7 +158,6 @@ auth_url = https://accounts.google.com/o/oauth2/auth
token_url = https://accounts.google.com/o/oauth2/token
api_url = https://www.googleapis.com/oauth2/v1/userinfo
allowed_domains =
allow_sign_up = false
#################################### Logging ##########################
[log]
......
......@@ -136,26 +136,27 @@
#################################### Github Auth ##########################
[auth.github]
;enabled = false
;allow_sign_up = false
;client_id = some_id
;client_secret = some_secret
;scopes = user:email
;auth_url = https://github.com/login/oauth/authorize
;token_url = https://github.com/login/oauth/access_token
;api_url = https://api.github.com/user
# Uncomment bellow to only allow specific email domains
; allowed_domains = mycompany.com othercompany.com
;team_ids =
;allowed_domains =
#################################### Google Auth ##########################
[auth.google]
;enabled = false
;allow_sign_up = false
;client_id = some_client_id
;client_secret = some_client_secret
;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
;auth_url = https://accounts.google.com/o/oauth2/auth
;token_url = https://accounts.google.com/o/oauth2/token
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
# Uncomment bellow to only allow specific email domains
; allowed_domains = mycompany.com othercompany.com
;allowed_domains =
#################################### Logging ##########################
[log]
......
......@@ -44,7 +44,7 @@ docs-test: docs-build
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
docs-build:
git fetch https://github.com/grafana/grafana.git docs-2.0 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
git fetch https://github.com/grafana/grafana.git docs-1.x && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
echo "$(GIT_BRANCH)" > GIT_BRANCH
echo "$(GITCOMMIT)" > GITCOMMIT
docker build -t "$(DOCKER_DOCS_IMAGE)" .
......@@ -15,10 +15,9 @@ no_toc: true
<div class="columns medium-6">
<h3>Episode 2 - Templated Graphite Queries</h3>
<div class="video-container">
<iframe height="315" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
<iframe height="215" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<div class="row">
......@@ -34,7 +33,6 @@ no_toc: true
<iframe height="215" src="https://www.youtube.com/embed/JY22EBOR9hQ?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<div class="row">
......@@ -50,7 +48,6 @@ no_toc: true
<iframe height="215" src="https://www.youtube.com/embed/9ZCMVNxUf6s?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<div class="row">
......
......@@ -182,6 +182,7 @@ Client ID and a Client Secret. Specify these in the grafana config file. Example
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
allow_sign_up = false
team_ids =
Restart the grafana backend. You should now see a github login button on the login page. You can
now login or signup with your github accounts.
......@@ -189,6 +190,21 @@ now login or signup with your github accounts.
You may allow users to sign-up via github auth by setting allow_sign_up to true. When this option is
set to true, any user successfully authenticating via github auth will be automatically signed up.
### team_ids
Require an active team membership for at least one of the given teams on GitHub.
If the authenticated user isn't a member of at least one the teams they will not
be able to register or authenticate with your Grafana instance. Example:
[auth.github]
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email
team_ids = 150,300
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
allow_sign_up = false
## [auth.google]
You need to create a google project. You can do this in the [Google Developer Console](https://console.developers.google.com/project).
When you create the project you will need to specify a callback URL. Specify this as callback:
......@@ -257,5 +273,3 @@ enabled. Counters are sent every 24 hours. Default value is `true`.
### google_analytics_ua_id
If you want to track Grafana usage via Google analytics specify *your* Univeral Analytics ID
here. By defualt this feature is disabled.
......@@ -16,7 +16,7 @@ Description | Download
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_2.0.1_amd64.deb
$ sudo dpkg -i grafana_2.0.2_amd64.deb
## APT Repository
Add the following line to your `/etc/apt/sources.list`
......
......@@ -21,8 +21,9 @@ type CurrentUser struct {
Email string `json:"email"`
Name string `json:"name"`
LightTheme bool `json:"lightTheme"`
OrgRole m.RoleType `json:"orgRole"`
OrgId int64 `json:"orgId"`
OrgName string `json:"orgName"`
OrgRole m.RoleType `json:"orgRole"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
GravatarUrl string `json:"gravatarUrl"`
}
......
......@@ -18,6 +18,7 @@ func setIndexViewData(c *middleware.Context) error {
Email: c.Email,
Name: c.Name,
LightTheme: c.Theme == "light",
OrgId: c.OrgId,
OrgName: c.OrgName,
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
......
......@@ -3,6 +3,7 @@ package api
import (
"errors"
"fmt"
"net/url"
"golang.org/x/oauth2"
......@@ -45,7 +46,11 @@ func OAuthLogin(ctx *middleware.Context) {
userInfo, err := connect.UserInfo(token)
if err != nil {
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
if err == social.ErrMissingTeamMembership {
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github team membership not fulfilled"))
} else {
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
}
return
}
......@@ -54,7 +59,7 @@ func OAuthLogin(ctx *middleware.Context) {
// validate that the email is allowed to login to grafana
if !connect.IsEmailAllowed(userInfo.Email) {
log.Info("OAuth login attempt with unallowed email, %s", userInfo.Email)
ctx.Redirect(setting.AppSubUrl + "/login?email_not_allowed=1")
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required email domain not fulfilled"))
return
}
......
......@@ -2,7 +2,9 @@ package social
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
......@@ -75,13 +77,24 @@ func NewOAuthService() {
// GitHub.
if name == "github" {
setting.OAuthService.GitHub = true
SocialMap["github"] = &SocialGithub{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup}
teamIds := sec.Key("team_ids").Ints(",")
SocialMap["github"] = &SocialGithub{
Config: &config,
allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup,
teamIds: teamIds,
}
}
// Google.
if name == "google" {
setting.OAuthService.Google = true
SocialMap["google"] = &SocialGoogle{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup}
SocialMap["google"] = &SocialGoogle{
Config: &config, allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup,
}
}
}
}
......@@ -103,10 +116,15 @@ func isEmailAllowed(email string, allowedDomains []string) bool {
type SocialGithub struct {
*oauth2.Config
allowedDomains []string
ApiUrl string
apiUrl string
allowSignup bool
teamIds []int
}
var (
ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
)
func (s *SocialGithub) Type() int {
return int(models.GITHUB)
}
......@@ -119,6 +137,28 @@ func (s *SocialGithub) IsSignupAllowed() bool {
return s.allowSignup
}
func (s *SocialGithub) IsTeamMember(client *http.Client, username string, teamId int) bool {
var data struct {
Url string `json:"url"`
State string `json:"state"`
}
membershipUrl := fmt.Sprintf("https://api.github.com/teams/%d/memberships/%s", teamId, username)
r, err := client.Get(membershipUrl)
if err != nil {
return false
}
defer r.Body.Close()
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
return false
}
active := data.State == "active"
return active
}
func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
......@@ -128,7 +168,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
var err error
client := s.Client(oauth2.NoContext, token)
r, err := client.Get(s.ApiUrl)
r, err := client.Get(s.apiUrl)
if err != nil {
return nil, err
}
......@@ -139,11 +179,23 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
return nil, err
}
return &BasicUserInfo{
userInfo := &BasicUserInfo{
Identity: strconv.Itoa(data.Id),
Name: data.Name,
Email: data.Email,
}, nil
}
if len(s.teamIds) > 0 {
for _, teamId := range s.teamIds {
if s.IsTeamMember(client, data.Name, teamId) {
return userInfo, nil
}
}
return nil, ErrMissingTeamMembership
} else {
return userInfo, nil
}
}
// ________ .__
......@@ -156,7 +208,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
type SocialGoogle struct {
*oauth2.Config
allowedDomains []string
ApiUrl string
apiUrl string
allowSignup bool
}
......@@ -181,7 +233,7 @@ func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
var err error
client := s.Client(oauth2.NoContext, token)
r, err := client.Get(s.ApiUrl)
r, err := client.Get(s.apiUrl)
if err != nil {
return nil, err
}
......
......@@ -376,6 +376,7 @@ function($, _, moment) {
kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
kbn.valueFormats.kbytes = kbn.formatFuncCreator(1024, [' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
kbn.valueFormats.mbytes = kbn.formatFuncCreator(1024, [' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
kbn.valueFormats.gbytes = kbn.formatFuncCreator(1024, [' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']);
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
......@@ -547,6 +548,7 @@ function($, _, moment) {
{text: 'bytes', value: 'bytes'},
{text: 'kilobytes', value: 'kbytes'},
{text: 'megabytes', value: 'mbytes'},
{text: 'gigabytes', value: 'gbytes'},
]
},
{
......
......@@ -7,7 +7,7 @@ function (angular, config) {
var module = angular.module('grafana.controllers');
module.controller('LoginCtrl', function($scope, backendSrv, contextSrv) {
module.controller('LoginCtrl', function($scope, backendSrv, contextSrv, $location) {
$scope.formModel = {
user: '',
email: '',
......@@ -28,6 +28,13 @@ function (angular, config) {
$scope.init = function() {
$scope.$watch("loginMode", $scope.loginModeChanged);
$scope.passwordChanged();
var params = $location.search();
if (params.failedMsg) {
$scope.appEvent('alert-warning', ['Login Failed', params.failedMsg]);
delete params.failedMsg;
$location.search(params);
}
};
// build info view model
......
......@@ -29,13 +29,7 @@ function (angular, _, kbn) {
var variable = this.variables[i];
var urlValue = queryParams['var-' + variable.name];
if (urlValue !== void 0) {
var option = _.findWhere(variable.options, { text: urlValue });
option = option || { text: urlValue, value: urlValue };
var promise = this.setVariableValue(variable, option);
this.updateAutoInterval(variable);
promises.push(promise);
promises.push(this.setVariableFromUrl(variable, urlValue));
}
else if (variable.refresh) {
promises.push(this.updateOptions(variable));
......@@ -48,6 +42,25 @@ function (angular, _, kbn) {
return $q.all(promises);
};
this.setVariableFromUrl = function(variable, urlValue) {
if (variable.refresh) {
var self = this;
//refresh the list of options before setting the value
return this.updateOptions(variable).then(function() {
var option = _.findWhere(variable.options, { text: urlValue });
option = option || { text: urlValue, value: urlValue };
self.updateAutoInterval(variable);
return self.setVariableValue(variable, option);
});
}
var option = _.findWhere(variable.options, { text: urlValue });
option = option || { text: urlValue, value: urlValue };
this.updateAutoInterval(variable);
return this.setVariableValue(variable, option);
};
this.updateAutoInterval = function(variable) {
if (!variable.auto) { return; }
......
......@@ -5,7 +5,8 @@ function (_) {
'use strict';
function InfluxSeries(options) {
this.seriesList = options.seriesList;
this.seriesList = options.seriesList && options.seriesList.results && options.seriesList.results.length > 0
? options.seriesList.results[0].series || [] : [];
this.alias = options.alias;
this.annotation = options.annotation;
}
......@@ -17,12 +18,10 @@ function (_) {
var self = this;
console.log(self.seriesList);
if (!self.seriesList || !self.seriesList.results || !self.seriesList.results[0]) {
if (self.seriesList.length === 0) {
return output;
}
this.seriesList = self.seriesList.results[0].series;
_.each(self.seriesList, function(series) {
var datapoints = [];
for (var i = 0; i < series.values.length; i++) {
......@@ -63,19 +62,15 @@ function (_) {
if (column === self.annotation.textColumn) { textCol = index; return; }
});
_.each(series.points, function (point) {
_.each(series.values, function (value) {
var data = {
annotation: self.annotation,
time: point[timeCol],
title: point[titleCol],
tags: point[tagsCol],
text: point[textCol]
time: + new Date(value[timeCol]),
title: value[titleCol],
tags: value[tagsCol],
text: value[textCol]
};
if (tagsCol) {
data.tags = point[tagsCol];
}
list.push(data);
});
});
......
......@@ -13,5 +13,6 @@
"annotations": "app/plugins/datasource/influxdb/partials/annotations.editor.html"
},
"metrics": true
"metrics": true,
"annotations": true
}
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