Merge branch 'master' into websocket

This commit is contained in:
Torkel Ödegaard
2017-01-31 08:56:49 +01:00
283 changed files with 29756 additions and 7727 deletions
+6 -2
View File
@@ -73,9 +73,9 @@ func GetAlerts(c *middleware.Context) Response {
Name: alert.Name,
Message: alert.Message,
State: alert.State,
EvalDate: alert.EvalDate,
NewStateDate: alert.NewStateDate,
ExecutionError: alert.ExecutionError,
EvalData: alert.EvalData,
})
}
@@ -121,10 +121,10 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response {
}
res := backendCmd.Result
dtoRes := &dtos.AlertTestResult{
Firing: res.Firing,
ConditionEvals: res.ConditionEvals,
State: res.Rule.State,
}
if res.Error != nil {
@@ -173,6 +173,10 @@ func DelAlert(c *middleware.Context) Response {
return Json(200, resp)
}
func GetAlertNotifiers(c *middleware.Context) Response {
return Json(200, alerting.GetNotifiers())
}
func GetAlertNotifications(c *middleware.Context) Response {
query := &models.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
+3
View File
@@ -125,6 +125,8 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/", wrap(SearchUsers))
r.Get("/:id", wrap(GetUserById))
r.Get("/:id/orgs", wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com
r.Get("/lookup", wrap(GetUserByLoginOrEmail))
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
r.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg))
}, reqGrafanaAdmin)
@@ -261,6 +263,7 @@ func (hs *HttpServer) registerRoutes() {
})
r.Get("/alert-notifications", wrap(GetAlertNotifications))
r.Get("/alert-notifiers", wrap(GetAlertNotifiers))
r.Group("/alert-notifications", func() {
r.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
+44 -17
View File
@@ -17,7 +17,6 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@@ -90,7 +89,7 @@ type cache struct {
var awsCredentialCache map[string]cache = make(map[string]cache)
var credentialCacheLock sync.RWMutex
func getCredentials(dsInfo *datasourceInfo) *credentials.Credentials {
func getCredentials(dsInfo *datasourceInfo) (*credentials.Credentials, error) {
cacheKey := dsInfo.Profile + ":" + dsInfo.AssumeRoleArn
credentialCacheLock.RLock()
if _, ok := awsCredentialCache[cacheKey]; ok {
@@ -98,7 +97,7 @@ func getCredentials(dsInfo *datasourceInfo) *credentials.Credentials {
(*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) {
result := awsCredentialCache[cacheKey].credential
credentialCacheLock.RUnlock()
return result
return result, nil
}
}
credentialCacheLock.RUnlock()
@@ -130,8 +129,7 @@ func getCredentials(dsInfo *datasourceInfo) *credentials.Credentials {
svc := sts.New(session.New(stsConfig), stsConfig)
resp, err := svc.AssumeRole(params)
if err != nil {
// ignore
log.Error(3, "CloudWatch: Failed to assume role", err)
return nil, err
}
if resp.Credentials != nil {
accessKeyId = *resp.Credentials.AccessKeyId
@@ -165,19 +163,28 @@ func getCredentials(dsInfo *datasourceInfo) *credentials.Credentials {
}
credentialCacheLock.Unlock()
return creds
return creds, nil
}
func getAwsConfig(req *cwRequest) *aws.Config {
func getAwsConfig(req *cwRequest) (*aws.Config, error) {
creds, err := getCredentials(req.GetDatasourceInfo())
if err != nil {
return nil, err
}
cfg := &aws.Config{
Region: aws.String(req.Region),
Credentials: getCredentials(req.GetDatasourceInfo()),
Credentials: creds,
}
return cfg
return cfg, nil
}
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -220,7 +227,11 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
}
func handleListMetrics(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -239,7 +250,7 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
}
var resp cloudwatch.ListMetricsOutput
err := svc.ListMetricsPages(params,
err = svc.ListMetricsPages(params,
func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
metrics.M_Aws_CloudWatch_ListMetrics.Inc(1)
metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
@@ -257,7 +268,11 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
}
func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -296,7 +311,11 @@ func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
}
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -336,7 +355,11 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
}
func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -368,7 +391,11 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
}
func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
cfg := getAwsConfig(req)
cfg, err := getAwsConfig(req)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
svc := ec2.New(session.New(cfg), cfg)
reqParam := &struct {
@@ -388,7 +415,7 @@ func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
}
var resp ec2.DescribeInstancesOutput
err := svc.DescribeInstancesPages(params,
err = svc.DescribeInstancesPages(params,
func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
reservations, _ := awsutil.ValuesAtPath(page, "Reservations")
for _, reservation := range reservations {
+9 -5
View File
@@ -111,7 +111,7 @@ func init() {
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
"AWS/ES": {"ClientId", "DomainName"},
"AWS/Events": {"RuleName"},
"AWS/Firehose": {},
"AWS/Firehose": {"DeliveryStreamName"},
"AWS/IoT": {"Protocol"},
"AWS/Kinesis": {"StreamName", "ShardID"},
"AWS/KinesisAnalytics": {"Flow", "Id", "Application"},
@@ -140,8 +140,8 @@ func init() {
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
func handleGetRegions(req *cwRequest, c *middleware.Context) {
regions := []string{
"ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "cn-north-1",
"eu-central-1", "eu-west-1", "sa-east-1", "us-east-1", "us-west-1", "us-west-2", "us-gov-west-1",
"ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-south-1", "ca-central-1", "cn-north-1",
"eu-central-1", "eu-west-1", "eu-west-2", "sa-east-1", "us-east-1", "us-east-2", "us-gov-west-1", "us-west-1", "us-west-2",
}
result := []interface{}{}
@@ -248,9 +248,13 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
}
func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error) {
creds, err := getCredentials(cwData)
if err != nil {
return cloudwatch.ListMetricsOutput{}, err
}
cfg := &aws.Config{
Region: aws.String(cwData.Region),
Credentials: getCredentials(cwData),
Credentials: creds,
}
svc := cloudwatch.New(session.New(cfg), cfg)
@@ -260,7 +264,7 @@ func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error)
}
var resp cloudwatch.ListMetricsOutput
err := svc.ListMetricsPages(params,
err = svc.ListMetricsPages(params,
func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
metrics.M_Aws_CloudWatch_ListMetrics.Inc(1)
metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
+33
View File
@@ -1,6 +1,8 @@
package api
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
@@ -8,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/cloudwatch"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@@ -15,6 +18,10 @@ import (
"github.com/grafana/grafana/pkg/util"
)
var (
dataproxyLogger log.Logger = log.New("data-proxy-log")
)
func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = targetUrl.Scheme
@@ -121,6 +128,32 @@ func ProxyDataSourceRequest(c *middleware.Context) {
c.JsonApiErr(400, "Unable to load TLS certificate", err)
return
}
logProxyRequest(ds.Type, c)
proxy.ServeHTTP(c.Resp, c.Req.Request)
c.Resp.Header().Del("Set-Cookie")
}
func logProxyRequest(dataSourceType string, c *middleware.Context) {
if !setting.DataProxyLogging {
return
}
var body string
if c.Req.Request.Body != nil {
buffer, err := ioutil.ReadAll(c.Req.Request.Body)
if err == nil {
c.Req.Request.Body = ioutil.NopCloser(bytes.NewBuffer(buffer))
body = string(buffer)
}
}
dataproxyLogger.Info("Proxying incoming request",
"userid", c.UserId,
"orgid", c.OrgId,
"username", c.Login,
"datasource", dataSourceType,
"uri", c.Req.RequestURI,
"method", c.Req.Request.Method,
"body", body)
}
+2 -2
View File
@@ -100,7 +100,7 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
return
}
c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id})
c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id, "name": cmd.Result.Name})
}
func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Response {
@@ -117,7 +117,7 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Resp
return ApiError(500, "Failed to update datasource", err)
}
return Json(200, util.DynMap{"message": "Datasource updated"})
return Json(200, util.DynMap{"message": "Datasource updated", "id": cmd.Id, "name": cmd.Name})
}
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
+4 -1
View File
@@ -3,6 +3,7 @@ package dtos
import (
"time"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
@@ -16,6 +17,7 @@ type AlertRule struct {
State m.AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"`
EvalDate time.Time `json:"evalDate"`
EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"`
DashbboardUri string `json:"dashboardUri"`
}
@@ -36,6 +38,7 @@ type AlertTestCommand struct {
type AlertTestResult struct {
Firing bool `json:"firing"`
State m.AlertStateType `json:"state"`
ConditionEvals string `json:"conditionEvals"`
TimeMs string `json:"timeMs"`
Error string `json:"error,omitempty"`
@@ -51,7 +54,7 @@ type AlertTestResultLog struct {
type EvalMatch struct {
Tags map[string]string `json:"tags,omitempty"`
Metric string `json:"metric"`
Value float64 `json:"value"`
Value null.Float `json:"value"`
}
type NotificationTestCommand struct {
+1 -1
View File
@@ -31,7 +31,7 @@ type AdminUpdateUserPasswordForm struct {
}
type AdminUpdateUserPermissionsForm struct {
IsGrafanaAdmin bool `json:"IsGrafanaAdmin"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin" binding:"Required"`
}
type AdminUserListItem struct {
+1
View File
@@ -140,6 +140,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,
+2 -2
View File
@@ -103,10 +103,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Children: dashboardChildNavs,
})
if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
if setting.AlertingEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
alertChildNavs := []*dtos.NavLink{
{Text: "Alert List", Url: setting.AppSubUrl + "/alerting/list"},
{Text: "Notifications", Url: setting.AppSubUrl + "/alerting/notifications"},
{Text: "Notification channels", Url: setting.AppSubUrl + "/alerting/notifications"},
}
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
+6 -5
View File
@@ -14,11 +14,12 @@ func RenderToPng(c *middleware.Context) {
queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
renderOpts := &renderer.RenderOpts{
Path: c.Params("*") + queryParams,
Width: queryReader.Get("width", "800"),
Height: queryReader.Get("height", "400"),
OrgId: c.OrgId,
Timeout: queryReader.Get("timeout", "30"),
Path: c.Params("*") + queryParams,
Width: queryReader.Get("width", "800"),
Height: queryReader.Get("height", "400"),
OrgId: c.OrgId,
Timeout: queryReader.Get("timeout", "30"),
Timezone: queryReader.Get("tz", ""),
}
pngPath, err := renderer.RenderToPng(renderOpts)
+30 -6
View File
@@ -13,7 +13,7 @@ func GetSignedInUser(c *middleware.Context) Response {
return getUserUserProfile(c.UserId)
}
// GET /api/user/:id
// GET /api/users/:id
func GetUserById(c *middleware.Context) Response {
return getUserUserProfile(c.ParamsInt64(":id"))
}
@@ -22,12 +22,36 @@ func getUserUserProfile(userId int64) Response {
query := m.GetUserProfileQuery{UserId: userId}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
return ApiError(404, m.ErrUserNotFound.Error(), nil)
}
return ApiError(500, "Failed to get user", err)
}
return Json(200, query.Result)
}
// GET /api/users/lookup
func GetUserByLoginOrEmail(c *middleware.Context) Response {
query := m.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
return ApiError(404, m.ErrUserNotFound.Error(), nil)
}
return ApiError(500, "Failed to get user", err)
}
user := query.Result
result := m.UserProfileDTO{
Name: user.Name,
Email: user.Email,
Login: user.Login,
Theme: user.Theme,
IsGrafanaAdmin: user.IsAdmin,
OrgId: user.OrgId,
}
return Json(200, &result)
}
// POST /api/user
func UpdateSignedInUser(c *middleware.Context, cmd m.UpdateUserCommand) Response {
if setting.AuthProxyEnabled {
@@ -60,7 +84,7 @@ func UpdateUserActiveOrg(c *middleware.Context) Response {
cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed change active organization", err)
return ApiError(500, "Failed to change active organization", err)
}
return ApiSuccess("Active organization changed")
@@ -70,12 +94,12 @@ func handleUpdateUser(cmd m.UpdateUserCommand) Response {
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
if len(cmd.Login) == 0 {
return ApiError(400, "Validation error, need specify either username or email", nil)
return ApiError(400, "Validation error, need to specify either username or email", nil)
}
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "failed to update user", err)
return ApiError(500, "Failed to update user", err)
}
return ApiSuccess("User updated")
@@ -95,7 +119,7 @@ func getUserOrgList(userId int64) Response {
query := m.GetUserOrgListQuery{UserId: userId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Faile to get user organziations", err)
return ApiError(500, "Failed to get user organizations", err)
}
return Json(200, query.Result)
@@ -130,7 +154,7 @@ func UserSetUsingOrg(c *middleware.Context) Response {
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed change active organization", err)
return ApiError(500, "Failed to change active organization", err)
}
return ApiSuccess("Active organization changed")