Commit a7645b71 by Torkel Ödegaard

Merge remote-tracking branch 'origin/master' into develop

parents 26b8483f 35106537
......@@ -51,6 +51,8 @@ From `/etc/grafana/datasources` to `/etc/grafana/provisioning/datasources` when
## Fixes
* **Gzip**: Fixes bug gravatar images when gzip was enabled [#5952](https://github.com/grafana/grafana/issues/5952)
* **Alert list**: Now shows alert state changes even after adding manual annotations on dashboard [#9951](https://github.com/grafana/grafana/issues/9951)
* **Alerting**: Fixes bug where rules evaluated as firing when all conditions was false and using OR operator. [#9318](https://github.com/grafana/grafana/issues/9318)
* **Cloudwatch**: CloudWatch no longer display metrics' default alias [#10151](https://github.com/grafana/grafana/issues/10151), thx [@mtanda](https://github.com/mtanda)
# 4.6.2 (2017-11-16)
......
......@@ -221,6 +221,9 @@ external_manage_link_url =
external_manage_link_name =
external_manage_info =
# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
viewers_can_edit = false
[auth]
# Set to true to disable (hide) the login form, useful if you use OAuth
disable_login_form = false
......
......@@ -205,6 +205,9 @@ log_queries =
;external_manage_link_name =
;external_manage_info =
# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
;viewers_can_edit = false
[auth]
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false
......
graphite:
graphite09:
build: blocks/graphite
ports:
- "8080:80"
......
......@@ -100,7 +100,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
JSON Body schema:
- **name** – The key name
- **role** – Sets the access level/Grafana Role for the key. Can be one of the following values: `Viewer`, `Editor`, `Read Only Editor` or `Admin`.
- **role** – Sets the access level/Grafana Role for the key. Can be one of the following values: `Viewer`, `Editor` or `Admin`.
**Example Response**:
......
......@@ -205,7 +205,7 @@ The database user (not applicable for `sqlite3`).
### password
The database user's password (not applicable for `sqlite3`). If the password contains `#` or `;` you have to wrap it with trippel quotes. Ex `"""#password;"""`
The database user's password (not applicable for `sqlite3`). If the password contains `#` or `;` you have to wrap it with triple quotes. Ex `"""#password;"""`
### ssl_mode
......@@ -214,19 +214,19 @@ For MySQL, use either `true`, `false`, or `skip-verify`.
### ca_cert_path
(MySQL only) The path to the CA certificate to use. On many linux systems, certs can be found in `/etc/ssl/certs`.
The path to the CA certificate to use. On many linux systems, certs can be found in `/etc/ssl/certs`.
### client_key_path
(MySQL only) The path to the client key. Only if server requires client authentication.
The path to the client key. Only if server requires client authentication.
### client_cert_path
(MySQL only) The path to the client cert. Only if server requires client authentication.
The path to the client cert. Only if server requires client authentication.
### server_cert_name
(MySQL only) The common name field of the certificate used by the `mysql` server. Not necessary if `ssl_mode` is set to `skip-verify`.
The common name field of the certificate used by the `mysql` or `postgres` server. Not necessary if `ssl_mode` is set to `skip-verify`.
### max_idle_conn
The maximum number of connections in the idle connection pool.
......@@ -292,10 +292,14 @@ organization to be created for that new user.
The role new users will be assigned for the main organization (if the
above setting is set to true). Defaults to `Viewer`, other valid
options are `Admin` and `Editor` and `Read Only Editor`. e.g. :
options are `Admin` and `Editor`. e.g. :
`auto_assign_org_role = Read Only Editor`
`auto_assign_org_role = Viewer`
### viewers can edit
Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
Defaults to `false`.
<hr>
......
......@@ -229,10 +229,6 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
return Json(200, util.DynMap{"status": "success", "slug": dashboard.Slug, "version": dashboard.Version, "id": dashboard.Id})
}
func canEditDashboard(role m.RoleType) bool {
return role == m.ROLE_ADMIN || role == m.ROLE_EDITOR || role == m.ROLE_READ_ONLY_EDITOR
}
func GetHomeDashboard(c *middleware.Context) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
......@@ -258,7 +254,7 @@ func GetHomeDashboard(c *middleware.Context) Response {
dash := dtos.DashboardFullWithMeta{}
dash.Meta.IsHome = true
dash.Meta.CanEdit = c.SignedInUser.HasRole(m.ROLE_READ_ONLY_EDITOR)
dash.Meta.CanEdit = c.SignedInUser.HasRole(m.ROLE_EDITOR)
dash.Meta.FolderTitle = "Root"
jsonParser := json.NewDecoder(file)
......
......@@ -18,14 +18,13 @@ var (
type RoleType string
const (
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_READ_ONLY_EDITOR RoleType = "Read Only Editor"
ROLE_ADMIN RoleType = "Admin"
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_ADMIN RoleType = "Admin"
)
func (r RoleType) IsValid() bool {
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}
func (r RoleType) Includes(other RoleType) bool {
......@@ -33,16 +32,8 @@ func (r RoleType) Includes(other RoleType) bool {
return true
}
if other == ROLE_READ_ONLY_EDITOR {
return r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR
}
if other == ROLE_EDITOR {
return r == ROLE_EDITOR
}
if other == ROLE_VIEWER {
return r == ROLE_READ_ONLY_EDITOR || r == ROLE_EDITOR || r == ROLE_VIEWER
if r == ROLE_EDITOR {
return other != ROLE_ADMIN
}
return false
......
......@@ -75,14 +75,6 @@ func (c *EvalContext) ShouldUpdateAlertState() bool {
return c.Rule.State != c.PrevAlertState
}
func (c *EvalContext) ShouldSendNotification() bool {
if (c.PrevAlertState == m.AlertStatePending) && (c.Rule.State == m.AlertStateOK) {
return false
}
return true
}
func (a *EvalContext) GetDurationMs() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
}
......
......@@ -28,21 +28,5 @@ func TestAlertingEvalContext(t *testing.T) {
So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})
Convey("Should send notifications", func() {
Convey("pending -> ok", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.State = models.AlertStateOK
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
})
})
}
......@@ -39,6 +39,11 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
break
}
if i == 0 {
firing = cr.Firing
noDataFound = cr.NoDataFound
}
// calculating Firing based on operator
if cr.Operator == "or" {
firing = firing || cr.Firing
......
......@@ -36,6 +36,16 @@ func TestAlertingEvaluationHandler(t *testing.T) {
So(context.ConditionEvals, ShouldEqual, "true = true")
})
Convey("Show return triggered with single passing condition2", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
})
handler.Eval(context)
So(context.Firing, ShouldEqual, true)
So(context.ConditionEvals, ShouldEqual, "true = true")
})
Convey("Show return false with not passing asdf", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
......@@ -131,6 +141,33 @@ func TestAlertingEvaluationHandler(t *testing.T) {
So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
})
Convey("Should return false if no condition is firing using OR operator", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
&conditionStub{firing: false, operator: "or"},
&conditionStub{firing: false, operator: "or"},
&conditionStub{firing: false, operator: "or"},
},
})
handler.Eval(context)
So(context.Firing, ShouldEqual, false)
So(context.ConditionEvals, ShouldEqual, "[[false OR false] OR false] = false")
})
Convey("Should retuasdfrn no data if one condition has nodata", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
&conditionStub{operator: "or", noData: false},
&conditionStub{operator: "or", noData: false},
&conditionStub{operator: "or", noData: false},
},
})
handler.Eval(context)
So(context.NoDataFound, ShouldBeFalse)
})
Convey("Should return no data if one condition has nodata", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
......
......@@ -15,7 +15,7 @@ type Notifier interface {
Notify(evalContext *EvalContext) error
GetType() string
NeedsImage() bool
PassesFilter(rule *Rule) bool
ShouldNotify(evalContext *EvalContext) bool
GetNotifierId() int64
GetIsDefault() bool
......
......@@ -24,7 +24,7 @@ type NotifierPlugin struct {
}
type NotificationService interface {
Send(context *EvalContext) error
SendIfNeeded(context *EvalContext) error
}
func NewNotificationService() NotificationService {
......@@ -41,14 +41,12 @@ func newNotificationService() *notificationService {
}
}
func (n *notificationService) Send(context *EvalContext) error {
notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
func (n *notificationService) SendIfNeeded(context *EvalContext) error {
notifiers, err := n.getNeededNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
if err != nil {
return err
}
n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "sent count", len(notifiers))
if len(notifiers) == 0 {
return nil
}
......@@ -110,7 +108,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
return nil
}
func (n *notificationService) getNotifiers(orgId int64, notificationIds []int64, context *EvalContext) (NotifierSlice, error) {
func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds []int64, context *EvalContext) (NotifierSlice, error) {
query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}
if err := bus.Dispatch(query); err != nil {
......@@ -122,7 +120,7 @@ func (n *notificationService) getNotifiers(orgId int64, notificationIds []int64,
if not, err := n.createNotifierFor(notification); err != nil {
return nil, err
} else {
if shouldUseNotification(not, context) {
if not.ShouldNotify(context) {
result = append(result, not)
}
}
......@@ -140,18 +138,6 @@ func (n *notificationService) createNotifierFor(model *m.AlertNotification) (Not
return notifierPlugin.Factory(model)
}
func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
if !context.Firing {
return true
}
if context.Error != nil {
return true
}
return notifier.PassesFilter(context.Rule)
}
type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)
var notifierFactories map[string]*NotifierPlugin = make(map[string]*NotifierPlugin)
......
package alerting
import (
"testing"
"fmt"
"github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
type FakeNotifier struct {
FakeMatchResult bool
}
func (fn *FakeNotifier) GetType() string {
return "FakeNotifier"
}
func (fn *FakeNotifier) NeedsImage() bool {
return true
}
func (n *FakeNotifier) GetNotifierId() int64 {
return 0
}
func (n *FakeNotifier) GetIsDefault() bool {
return false
}
func (fn *FakeNotifier) Notify(alertResult *EvalContext) error { return nil }
func (fn *FakeNotifier) PassesFilter(rule *Rule) bool {
return fn.FakeMatchResult
}
func TestAlertNotificationExtraction(t *testing.T) {
Convey("Notifier tests", t, func() {
Convey("none firing alerts", func() {
ctx := &EvalContext{
Firing: false,
Rule: &Rule{
State: m.AlertStateAlerting,
},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("execution error cannot be ignored", func() {
ctx := &EvalContext{
Firing: true,
Error: fmt.Errorf("I used to be a programmer just like you"),
Rule: &Rule{
State: m.AlertStateOK,
},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("firing alert that match", func() {
ctx := &EvalContext{
Firing: true,
Rule: &Rule{
State: models.AlertStateAlerting,
},
}
notifier := &FakeNotifier{FakeMatchResult: true}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("firing alert that dont match", func() {
ctx := &EvalContext{
Firing: true,
Rule: &Rule{State: m.AlertStateOK},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeFalse)
})
})
}
......@@ -2,6 +2,7 @@ package notifiers
import (
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
)
......@@ -25,7 +26,13 @@ func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model
}
}
func (n *NotifierBase) PassesFilter(rule *alerting.Rule) bool {
func defaultShouldNotify(context *alerting.EvalContext) bool {
if context.PrevAlertState == context.Rule.State {
return false
}
if (context.PrevAlertState == m.AlertStatePending) && (context.Rule.State == m.AlertStateOK) {
return false
}
return true
}
......
package notifiers
import (
"context"
"testing"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
)
func TestBaseNotifier(t *testing.T) {
Convey("Base notifier tests", t, func() {
Convey("should notify", func() {
Convey("pending -> ok", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStatePending,
})
context.Rule.State = m.AlertStateOK
So(defaultShouldNotify(context), ShouldBeFalse)
})
Convey("ok -> alerting", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStateOK,
})
context.Rule.State = m.AlertStateAlerting
So(defaultShouldNotify(context), ShouldBeTrue)
})
})
})
}
......@@ -38,6 +38,10 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}, nil
}
func (this *DingDingNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
type DingDingNotifier struct {
NotifierBase
Url string
......
......@@ -9,7 +9,7 @@ import (
)
func TestDingDingNotifier(t *testing.T) {
Convey("Line notifier tests", t, func() {
Convey("Dingding notifier tests", t, func() {
Convey("empty settings should return error", func() {
json := `{ }`
......
......@@ -58,6 +58,10 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}, nil
}
func (this *EmailNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending alert notification to", "addresses", this.Addresses)
......
......@@ -75,6 +75,10 @@ type HipChatNotifier struct {
log log.Logger
}
func (this *HipChatNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
......
......@@ -57,6 +57,10 @@ type KafkaNotifier struct {
log log.Logger
}
func (this *KafkaNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
state := evalContext.Rule.State
......
......@@ -51,6 +51,10 @@ type LineNotifier struct {
log log.Logger
}
func (this *LineNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *LineNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
......
......@@ -62,6 +62,10 @@ type OpsGenieNotifier struct {
log log.Logger
}
func (this *OpsGenieNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
var err error
......
......@@ -63,6 +63,10 @@ type PagerdutyNotifier struct {
log log.Logger
}
func (this *PagerdutyNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
if evalContext.Rule.State == m.AlertStateOK && !this.AutoResolve {
......
......@@ -123,6 +123,10 @@ type PushoverNotifier struct {
log log.Logger
}
func (this *PushoverNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
ruleUrl, err := evalContext.GetRuleUrl()
if err != nil {
......
......@@ -71,6 +71,10 @@ type SensuNotifier struct {
log log.Logger
}
func (this *SensuNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending sensu result")
......
......@@ -98,6 +98,10 @@ type SlackNotifier struct {
log log.Logger
}
func (this *SlackNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
......
......@@ -78,7 +78,6 @@ func TestSlackNotifier(t *testing.T) {
So(slackNotifier.Mention, ShouldEqual, "@carl")
So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX")
})
})
})
}
......@@ -47,6 +47,10 @@ type TeamsNotifier struct {
log log.Logger
}
func (this *TeamsNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
......
......@@ -76,6 +76,10 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}, nil
}
func (this *TelegramNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending alert notification to", "bot_token", this.BotToken)
this.log.Info("Sending alert notification to", "chat_id", this.ChatID)
......
......@@ -114,6 +114,10 @@ func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}, nil
}
func (this *ThreemaNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error {
notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID)
notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID)
......
......@@ -68,6 +68,10 @@ type VictoropsNotifier struct {
log log.Logger
}
func (this *VictoropsNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
// Notify sends notification to Victorops via POST to URL endpoint
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
......
......@@ -65,6 +65,10 @@ type WebhookNotifier struct {
log log.Logger
}
func (this *WebhookNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending webhook")
......
......@@ -18,7 +18,7 @@ func TestWebhookNotifier(t *testing.T) {
settingsJSON, _ := simplejson.NewJson([]byte(json))
model := &m.AlertNotification{
Name: "ops",
Type: "email",
Type: "webhook",
Settings: settingsJSON,
}
......@@ -35,7 +35,7 @@ func TestWebhookNotifier(t *testing.T) {
settingsJSON, _ := simplejson.NewJson([]byte(json))
model := &m.AlertNotification{
Name: "ops",
Type: "email",
Type: "webhook",
Settings: settingsJSON,
}
......@@ -44,7 +44,7 @@ func TestWebhookNotifier(t *testing.T) {
So(err, ShouldBeNil)
So(webhookNotifier.Name, ShouldEqual, "ops")
So(webhookNotifier.Type, ShouldEqual, "email")
So(webhookNotifier.Type, ShouldEqual, "webhook")
So(webhookNotifier.Url, ShouldEqual, "http://google.com")
})
})
......
......@@ -85,11 +85,9 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
if err := annotationRepo.Save(&item); err != nil {
handler.log.Error("Failed to save annotation for new alert state", "error", err)
}
if evalContext.ShouldSendNotification() {
handler.notifier.Send(evalContext)
}
}
handler.notifier.SendIfNeeded(evalContext)
return nil
}
......@@ -51,10 +51,6 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
}
orgRole := g.user.OrgRole
if orgRole == m.ROLE_READ_ONLY_EDITOR {
orgRole = m.ROLE_VIEWER
}
teamAclItems := []*m.DashboardAclInfoDTO{}
for _, p := range acl {
......
......@@ -83,4 +83,10 @@ func addOrgMigrations(mg *Migrator) {
mg.AddMigration("Update org_user table charset", NewTableCharsetMigration("org_user", []*Column{
{Name: "role", Type: DB_NVarchar, Length: 20},
}))
const migrateReadOnlyViewersToViewers = `UPDATE org_user SET role = 'Viewer' WHERE role = 'Read Only Editor'`
mg.AddMigration("Migrate all Read Only Viewers to Viewers", new(RawSqlMigration).
Sqlite(migrateReadOnlyViewersToViewers).
Postgres(migrateReadOnlyViewersToViewers).
Mysql(migrateReadOnlyViewersToViewers))
}
......@@ -106,6 +106,7 @@ var (
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
ViewersCanEdit bool
// Http auth
AdminUser string
......@@ -540,13 +541,14 @@ func NewConfigContext(args *CommandLineArgs) error {
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String()
DefaultTheme = users.Key("default_theme").String()
ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
ExternalUserMngLinkName = users.Key("external_manage_link_name").String()
ExternalUserMngInfo = users.Key("external_manage_info").String()
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
// auth
auth := Cfg.Section("auth")
......
......@@ -29,7 +29,7 @@
<td>
<div class="gf-form">
<span class="gf-form-select-wrapper">
<select type="text" ng-model="orgUser.role" class="gf-form-input max-width-8" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
<select type="text" ng-model="orgUser.role" class="gf-form-input max-width-8" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
</select>
</span>
</div>
......
......@@ -59,10 +59,10 @@
<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs" required class="gf-form-input max-width-20" placeholder="organization name">
</div>
<div class="gf-form">
<span class="gf-form-label">Role</span>
<span class="gf-form-select-wrapper">
<select type="text" ng-model="newOrg.role" class="gf-form-input width-10" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"></select>
</span>
<span class="gf-form-label">Role</span>
<span class="gf-form-select-wrapper">
<select type="text" ng-model="newOrg.role" class="gf-form-input width-10" ng-options="f for f in ['Viewer', 'Editor', 'Admin']"></select>
</span>
</div>
<div class="gf-form">
<button class="btn btn-success gf-form-btn" ng-click="addOrgUser()">Add</button>
......@@ -85,7 +85,7 @@
<td>
<div class="gf-form">
<span class="gf-form-select-wrapper">
<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(org)">
<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(org)">
</select>
</span>
</div>
......
......@@ -21,7 +21,7 @@
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Role</span>
<select ng-model="ctrl.invite.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']">
<select ng-model="ctrl.invite.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']">
</select>
</div>
......
......@@ -52,7 +52,7 @@
<td>{{user.lastSeenAtAge}}</td>
<td>
<div class="gf-form-select-wrapper width-12">
<select type="text" ng-model="user.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
<select type="text" ng-model="user.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
</select>
</div>
</td>
......
......@@ -120,14 +120,6 @@ export default class GraphiteQuery {
this.segments.push({value: "select metric"});
}
hasSelectMetric() {
if (this.segments.length > 0) {
return this.segments[this.segments.length - 1].value === 'select metric';
} else {
return false;
}
}
addFunction(newFunc) {
this.functions.push(newFunc);
this.moveAliasFuncLast();
......
......@@ -218,7 +218,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
var oldTarget = this.queryModel.target.target;
this.updateModelTarget();
if (this.queryModel.target !== oldTarget && !this.queryModel.hasSelectMetric()) {
if (this.queryModel.target !== oldTarget) {
this.panelCtrl.refresh();
}
}
......
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