Merge branch 'master' into feature/discord

This commit is contained in:
Andrzej Ressel
2018-04-08 20:09:56 +02:00
4486 changed files with 636351 additions and 459241 deletions
+31 -5
View File
@@ -159,10 +159,6 @@ type SetAlertStateCommand struct {
Timestamp time.Time
}
type DeleteAlertCommand struct {
AlertId int64
}
//Queries
type GetAlertsQuery struct {
OrgId int64
@@ -170,8 +166,9 @@ type GetAlertsQuery struct {
DashboardId int64
PanelId int64
Limit int64
User *SignedInUser
Result []*Alert
Result []*AlertListItemDTO
}
type GetAllAlertsQuery struct {
@@ -191,6 +188,21 @@ type GetAlertStatesForDashboardQuery struct {
Result []*AlertStateInfoDTO
}
type AlertListItemDTO struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
DashboardUid string `json:"dashboardUid"`
DashboardSlug string `json:"dashboardSlug"`
PanelId int64 `json:"panelId"`
Name string `json:"name"`
State AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"`
EvalDate time.Time `json:"evalDate"`
EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"`
Url string `json:"url"`
}
type AlertStateInfoDTO struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
@@ -198,3 +210,17 @@ type AlertStateInfoDTO struct {
State AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"`
}
// "Internal" commands
type UpdateDashboardAlertsCommand struct {
UserId int64
OrgId int64
Dashboard *Dashboard
}
type ValidateDashboardAlertsCommand struct {
UserId int64
OrgId int64
Dashboard *Dashboard
}
+86
View File
@@ -0,0 +1,86 @@
package models
import (
"strings"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
)
type ReqContext struct {
*macaron.Context
*SignedInUser
Session session.SessionStore
IsSignedIn bool
IsRenderCall bool
AllowAnonymous bool
Logger log.Logger
}
// Handle handles and logs error by given status.
func (ctx *ReqContext) Handle(status int, title string, err error) {
if err != nil {
ctx.Logger.Error(title, "error", err)
if setting.Env != setting.PROD {
ctx.Data["ErrorMsg"] = err
}
}
ctx.Data["Title"] = title
ctx.Data["AppSubUrl"] = setting.AppSubUrl
ctx.Data["Theme"] = "dark"
ctx.HTML(status, "error")
}
func (ctx *ReqContext) JsonOK(message string) {
resp := make(map[string]interface{})
resp["message"] = message
ctx.JSON(200, resp)
}
func (ctx *ReqContext) IsApiRequest() bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api")
}
func (ctx *ReqContext) JsonApiErr(status int, message string, err error) {
resp := make(map[string]interface{})
if err != nil {
ctx.Logger.Error(message, "error", err)
if setting.Env != setting.PROD {
resp["error"] = err.Error()
}
}
switch status {
case 404:
resp["message"] = "Not Found"
case 500:
resp["message"] = "Internal Server Error"
}
if message != "" {
resp["message"] = message
}
ctx.JSON(status, resp)
}
func (ctx *ReqContext) HasUserRole(role RoleType) bool {
return ctx.OrgRole.Includes(role)
}
func (ctx *ReqContext) HasHelpFlag(flag HelpFlags1) bool {
return ctx.HelpFlags1.HasFlag(flag)
}
func (ctx *ReqContext) TimeRequest(timer prometheus.Summary) {
ctx.Data["perfmon.timer"] = timer
}
+108
View File
@@ -0,0 +1,108 @@
package models
import (
"errors"
"time"
)
type PermissionType int
const (
PERMISSION_VIEW PermissionType = 1 << iota
PERMISSION_EDIT
PERMISSION_ADMIN
)
func (p PermissionType) String() string {
names := map[int]string{
int(PERMISSION_VIEW): "View",
int(PERMISSION_EDIT): "Edit",
int(PERMISSION_ADMIN): "Admin",
}
return names[int(p)]
}
// Typed errors
var (
ErrDashboardAclInfoMissing = errors.New("User id and team id cannot both be empty for a dashboard permission.")
ErrDashboardPermissionDashboardEmpty = errors.New("Dashboard Id must be greater than zero for a dashboard permission.")
ErrFolderAclInfoMissing = errors.New("User id and team id cannot both be empty for a folder permission.")
ErrFolderPermissionFolderEmpty = errors.New("Folder Id must be greater than zero for a folder permission.")
)
// Dashboard ACL model
type DashboardAcl struct {
Id int64
OrgId int64
DashboardId int64
UserId int64
TeamId int64
Role *RoleType // pointer to be nullable
Permission PermissionType
Created time.Time
Updated time.Time
}
type DashboardAclInfoDTO struct {
OrgId int64 `json:"-"`
DashboardId int64 `json:"dashboardId,omitempty"`
FolderId int64 `json:"folderId,omitempty"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
UserId int64 `json:"userId"`
UserLogin string `json:"userLogin"`
UserEmail string `json:"userEmail"`
TeamId int64 `json:"teamId"`
Team string `json:"team"`
Role *RoleType `json:"role,omitempty"`
Permission PermissionType `json:"permission"`
PermissionName string `json:"permissionName"`
Uid string `json:"uid"`
Title string `json:"title"`
Slug string `json:"slug"`
IsFolder bool `json:"isFolder"`
Url string `json:"url"`
}
func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool {
if dto.Role == nil || other.Role == nil {
return false
}
return dto.UserId <= 0 && dto.TeamId <= 0 && dto.UserId == other.UserId && dto.TeamId == other.TeamId && *dto.Role == *other.Role
}
func (dto *DashboardAclInfoDTO) hasSameUserAs(other *DashboardAclInfoDTO) bool {
return dto.UserId > 0 && dto.UserId == other.UserId
}
func (dto *DashboardAclInfoDTO) hasSameTeamAs(other *DashboardAclInfoDTO) bool {
return dto.TeamId > 0 && dto.TeamId == other.TeamId
}
// IsDuplicateOf returns true if other item has same role, same user or same team
func (dto *DashboardAclInfoDTO) IsDuplicateOf(other *DashboardAclInfoDTO) bool {
return dto.hasSameRoleAs(other) || dto.hasSameUserAs(other) || dto.hasSameTeamAs(other)
}
//
// COMMANDS
//
type UpdateDashboardAclCommand struct {
DashboardId int64
Items []*DashboardAcl
}
//
// QUERIES
//
type GetDashboardAclInfoListQuery struct {
DashboardId int64
OrgId int64
Result []*DashboardAclInfoDTO
}
+21
View File
@@ -0,0 +1,21 @@
package models
import (
"testing"
"fmt"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardAclModel(t *testing.T) {
Convey("When printing a PermissionType", t, func() {
view := PERMISSION_VIEW
printed := fmt.Sprint(view)
Convey("Should output a friendly name", func() {
So(printed, ShouldEqual, "View")
})
})
}
+9 -5
View File
@@ -64,20 +64,24 @@ type DeleteDashboardSnapshotCommand struct {
}
type DeleteExpiredSnapshotsCommand struct {
DeletedRows int64
}
type GetDashboardSnapshotQuery struct {
Key string
Key string
DeleteKey string
Result *DashboardSnapshot
}
type DashboardSnapshots []*DashboardSnapshot
type DashboardSnapshotsList []*DashboardSnapshotDTO
type GetDashboardSnapshotsQuery struct {
Name string
Limit int
OrgId int64
Name string
Limit int
OrgId int64
SignedInUser *SignedInUser
Result DashboardSnapshots
Result DashboardSnapshotsList
}
+79
View File
@@ -0,0 +1,79 @@
package models
import (
"errors"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
var (
ErrDashboardVersionNotFound = errors.New("Dashboard version not found")
ErrNoVersionsForDashboardId = errors.New("No dashboard versions found for the given DashboardId")
)
// A DashboardVersion represents the comparable data in a dashboard, allowing
// diffs of the dashboard to be performed.
type DashboardVersion struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
ParentVersion int `json:"parentVersion"`
RestoredFrom int `json:"restoredFrom"`
Version int `json:"version"`
Created time.Time `json:"created"`
CreatedBy int64 `json:"createdBy"`
Message string `json:"message"`
Data *simplejson.Json `json:"data"`
}
// DashboardVersionMeta extends the dashboard version model with the names
// associated with the UserIds, overriding the field with the same name from
// the DashboardVersion model.
type DashboardVersionMeta struct {
DashboardVersion
CreatedBy string `json:"createdBy"`
}
// DashboardVersionDTO represents a dashboard version, without the dashboard
// map.
type DashboardVersionDTO struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
ParentVersion int `json:"parentVersion"`
RestoredFrom int `json:"restoredFrom"`
Version int `json:"version"`
Created time.Time `json:"created"`
CreatedBy string `json:"createdBy"`
Message string `json:"message"`
}
//
// Queries
//
type GetDashboardVersionQuery struct {
DashboardId int64
OrgId int64
Version int
Result *DashboardVersion
}
type GetDashboardVersionsQuery struct {
DashboardId int64
OrgId int64
Limit int
Start int
Result []*DashboardVersionDTO
}
//
// Commands
//
type DeleteExpiredVersionsCommand struct {
DeletedRows int64
}
+190 -21
View File
@@ -2,20 +2,37 @@ package models
import (
"errors"
"fmt"
"strings"
"time"
"github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
)
// Typed errors
var (
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardFolderNotFound = errors.New("Folder not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards")
ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder")
ErrDashboardFolderNameExists = errors.New("A folder with that name already exists")
ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard")
ErrDashboardInvalidUid = errors.New("uid contains illegal characters")
ErrDashboardUidToLong = errors.New("uid to long. max 40 characters")
RootFolderName = "General"
)
type UpdatePluginDashboardError struct {
@@ -36,6 +53,7 @@ var (
// Dashboard model
type Dashboard struct {
Id int64
Uid string
Slug string
OrgId int64
GnetId int64
@@ -47,11 +65,38 @@ type Dashboard struct {
UpdatedBy int64
CreatedBy int64
FolderId int64
IsFolder bool
HasAcl bool
Title string
Data *simplejson.Json
}
func (d *Dashboard) SetId(id int64) {
d.Id = id
d.Data.Set("id", id)
}
func (d *Dashboard) SetUid(uid string) {
d.Uid = uid
d.Data.Set("uid", uid)
}
func (d *Dashboard) SetVersion(version int) {
d.Version = version
d.Data.Set("version", version)
}
// GetDashboardIdForSavePermissionCheck return the dashboard id to be used for checking permission of dashboard
func (d *Dashboard) GetDashboardIdForSavePermissionCheck() int64 {
if d.Id == 0 {
return d.FolderId
}
return d.Id
}
// NewDashboard creates a new dashboard
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
@@ -64,6 +109,16 @@ func NewDashboard(title string) *Dashboard {
return dash
}
// NewDashboardFolder creates a new dashboard folder
func NewDashboardFolder(title string) *Dashboard {
folder := NewDashboard(title)
folder.IsFolder = true
folder.Data.Set("schemaVersion", 16)
folder.Data.Set("version", 0)
folder.IsFolder = true
return folder
}
// GetTags turns the tags in data json into go string array
func (dash *Dashboard) GetTags() []string {
return dash.Data.Get("tags").MustStringArray()
@@ -74,14 +129,21 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
dash.Data = data
dash.Title = dash.Data.Get("title").MustString()
dash.UpdateSlug()
update := false
if id, err := dash.Data.Get("id").Float64(); err == nil {
dash.Id = int64(id)
update = true
}
if version, err := dash.Data.Get("version").Float64(); err == nil {
dash.Version = int(version)
dash.Updated = time.Now()
}
if uid, err := dash.Data.Get("uid").String(); err == nil {
dash.Uid = uid
update = true
}
if version, err := dash.Data.Get("version").Float64(); err == nil && update {
dash.Version = int(version)
dash.Updated = time.Now()
} else {
dash.Data.Set("version", 0)
dash.Created = time.Now()
@@ -98,14 +160,17 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
// GetDashboardModel turns the command into the savable model
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash := NewDashboardFromJson(cmd.Dashboard)
userId := cmd.UserId
if dash.Data.Get("version").MustInt(0) == 0 {
dash.CreatedBy = cmd.UserId
if userId == 0 {
userId = -1
}
dash.UpdatedBy = cmd.UserId
dash.UpdatedBy = userId
dash.OrgId = cmd.OrgId
dash.PluginId = cmd.PluginId
dash.IsFolder = cmd.IsFolder
dash.FolderId = cmd.FolderId
dash.UpdateSlug()
return dash
}
@@ -117,8 +182,46 @@ func (dash *Dashboard) GetString(prop string, defaultValue string) string {
// UpdateSlug updates the slug
func (dash *Dashboard) UpdateSlug() {
title := strings.ToLower(dash.Data.Get("title").MustString())
dash.Slug = slug.Make(title)
title := dash.Data.Get("title").MustString()
dash.Slug = SlugifyTitle(title)
}
func SlugifyTitle(title string) string {
return slug.Make(strings.ToLower(title))
}
// GetUrl return the html url for a folder if it's folder, otherwise for a dashboard
func (dash *Dashboard) GetUrl() string {
return GetDashboardFolderUrl(dash.IsFolder, dash.Uid, dash.Slug)
}
// Return the html url for a dashboard
func (dash *Dashboard) GenerateUrl() string {
return GetDashboardUrl(dash.Uid, dash.Slug)
}
// GetDashboardFolderUrl return the html url for a folder if it's folder, otherwise for a dashboard
func GetDashboardFolderUrl(isFolder bool, uid string, slug string) string {
if isFolder {
return GetFolderUrl(uid, slug)
}
return GetDashboardUrl(uid, slug)
}
// GetDashboardUrl return the html url for a dashboard
func GetDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%s/d/%s/%s", setting.AppSubUrl, uid, slug)
}
// GetFullDashboardUrl return the full url for a dashboard
func GetFullDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%sd/%s/%s", setting.AppUrl, uid, slug)
}
// GetFolderUrl return the html url for a folder
func GetFolderUrl(folderUid string, slug string) string {
return fmt.Sprintf("%s/dashboards/f/%s/%s", setting.AppSubUrl, folderUid, slug)
}
//
@@ -126,26 +229,55 @@ func (dash *Dashboard) UpdateSlug() {
//
type SaveDashboardCommand struct {
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
PluginId string `json:"-"`
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
Overwrite bool `json:"overwrite"`
Message string `json:"message"`
OrgId int64 `json:"-"`
RestoredFrom int `json:"-"`
PluginId string `json:"-"`
FolderId int64 `json:"folderId"`
IsFolder bool `json:"isFolder"`
UpdatedAt time.Time
Result *Dashboard
}
type DashboardProvisioning struct {
Id int64
DashboardId int64
Name string
ExternalId string
Updated int64
}
type SaveProvisionedDashboardCommand struct {
DashboardCmd *SaveDashboardCommand
DashboardProvisioning *DashboardProvisioning
Result *Dashboard
}
type DeleteDashboardCommand struct {
Slug string
Id int64
OrgId int64
}
type ValidateDashboardBeforeSaveCommand struct {
OrgId int64
Dashboard *Dashboard
Overwrite bool
}
//
// QUERIES
//
type GetDashboardQuery struct {
Slug string
Slug string // required if no Id or Uid is specified
Id int64 // optional if slug is set
Uid string // optional if slug is set
OrgId int64
Result *Dashboard
@@ -166,6 +298,14 @@ type GetDashboardsQuery struct {
Result []*Dashboard
}
type GetDashboardPermissionsForUserQuery struct {
DashboardIds []int64
OrgId int64
UserId int64
OrgRole RoleType
Result []*DashboardPermissionForUser
}
type GetDashboardsByPluginIdQuery struct {
OrgId int64
PluginId string
@@ -176,3 +316,32 @@ type GetDashboardSlugByIdQuery struct {
Id int64
Result string
}
type GetProvisionedDashboardDataQuery struct {
Name string
Result []*DashboardProvisioning
}
type GetDashboardsBySlugQuery struct {
OrgId int64
Slug string
Result []*Dashboard
}
type DashboardPermissionForUser struct {
DashboardId int64 `json:"dashboardId"`
Permission PermissionType `json:"permission"`
PermissionName string `json:"permissionName"`
}
type DashboardRef struct {
Uid string
Slug string
}
type GetDashboardRefByIdQuery struct {
Id int64
Result *DashboardRef
}
+42
View File
@@ -4,11 +4,24 @@ import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardModel(t *testing.T) {
Convey("Generate full dashboard url", t, func() {
setting.AppUrl = "http://grafana.local/"
fullUrl := GetFullDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "http://grafana.local/d/uid/my-dashboard")
})
Convey("Generate relative dashboard url", t, func() {
setting.AppUrl = ""
fullUrl := GetDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "/d/uid/my-dashboard")
})
Convey("When generating slug", t, func() {
dashboard := NewDashboard("Grafana Play Home")
dashboard.UpdateSlug()
@@ -16,6 +29,12 @@ func TestDashboardModel(t *testing.T) {
So(dashboard.Slug, ShouldEqual, "grafana-play-home")
})
Convey("Can slugify title", t, func() {
slug := SlugifyTitle("Grafana Play Home")
So(slug, ShouldEqual, "grafana-play-home")
})
Convey("Given a dashboard json", t, func() {
json := simplejson.New()
json.Set("title", "test dash")
@@ -28,4 +47,27 @@ func TestDashboardModel(t *testing.T) {
})
})
Convey("Given a new dashboard folder", t, func() {
json := simplejson.New()
json.Set("title", "test dash")
cmd := &SaveDashboardCommand{Dashboard: json, IsFolder: true}
dash := cmd.GetDashboardModel()
Convey("Should set IsFolder to true", func() {
So(dash.IsFolder, ShouldBeTrue)
})
})
Convey("Given a child dashboard", t, func() {
json := simplejson.New()
json.Set("title", "test dash")
cmd := &SaveDashboardCommand{Dashboard: json, FolderId: 1}
dash := cmd.GetDashboardModel()
Convey("Should set FolderId", func() {
So(dash.FolderId, ShouldEqual, 1)
})
})
}
+50 -16
View File
@@ -17,14 +17,18 @@ const (
DS_CLOUDWATCH = "cloudwatch"
DS_KAIROSDB = "kairosdb"
DS_PROMETHEUS = "prometheus"
DS_POSTGRES = "postgres"
DS_MYSQL = "mysql"
DS_MSSQL = "mssql"
DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy"
)
// Typed errors
var (
ErrDataSourceNotFound = errors.New("Data source not found")
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
ErrDataSourceNotFound = errors.New("Data source not found")
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource")
ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration.")
)
type DsAccess string
@@ -48,25 +52,42 @@ type DataSource struct {
IsDefault bool
JsonData *simplejson.Json
SecureJsonData securejsondata.SecureJsonData
ReadOnly bool
Created time.Time
Updated time.Time
}
var knownDatasourcePlugins map[string]bool = map[string]bool{
DS_ES: true,
DS_GRAPHITE: true,
DS_INFLUXDB: true,
DS_INFLUXDB_08: true,
DS_KAIROSDB: true,
DS_CLOUDWATCH: true,
DS_PROMETHEUS: true,
DS_OPENTSDB: true,
"opennms": true,
"druid": true,
"dalmatinerdb": true,
"gnocci": true,
"zabbix": true,
DS_ES: true,
DS_GRAPHITE: true,
DS_INFLUXDB: true,
DS_INFLUXDB_08: true,
DS_KAIROSDB: true,
DS_CLOUDWATCH: true,
DS_PROMETHEUS: true,
DS_OPENTSDB: true,
DS_POSTGRES: true,
DS_MYSQL: true,
DS_MSSQL: true,
"opennms": true,
"abhisant-druid-datasource": true,
"dalmatinerdb-datasource": true,
"gnocci": true,
"zabbix": true,
"alexanderzobnin-zabbix-datasource": true,
"newrelic-app": true,
"grafana-datadog-datasource": true,
"grafana-simple-json": true,
"grafana-splunk-datasource": true,
"udoprog-heroic-datasource": true,
"grafana-openfalcon-datasource": true,
"opennms-datasource": true,
"rackerlabs-blueflood-datasource": true,
"crate-datasource": true,
"ayoungprogrammer-finance-datasource": true,
"monasca-datasource": true,
"vertamedia-clickhouse-datasource": true,
}
func IsKnownDataSourcePlugin(dsType string) bool {
@@ -93,6 +114,7 @@ type AddDataSourceCommand struct {
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"`
ReadOnly bool `json:"readOnly"`
OrgId int64 `json:"-"`
@@ -115,19 +137,27 @@ type UpdateDataSourceCommand struct {
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"`
Version int `json:"version"`
ReadOnly bool `json:"readOnly"`
OrgId int64 `json:"-"`
Id int64 `json:"-"`
Result *DataSource
}
type DeleteDataSourceByIdCommand struct {
Id int64
OrgId int64
DeletedDatasourcesCount int64
}
type DeleteDataSourceByNameCommand struct {
Name string
OrgId int64
DeletedDatasourcesCount int64
}
// ---------------------
@@ -138,6 +168,10 @@ type GetDataSourcesQuery struct {
Result []*DataSource
}
type GetAllDataSourcesQuery struct {
Result []*DataSource
}
type GetDataSourceByIdQuery struct {
Id int64
OrgId int64
+21 -17
View File
@@ -3,6 +3,7 @@ package models
import (
"crypto/tls"
"crypto/x509"
"errors"
"net"
"net/http"
"sync"
@@ -45,14 +46,23 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
return t.Transport, nil
}
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
if ds.JsonData != nil {
tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false)
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
InsecureSkipVerify: tlsSkipVerify,
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
@@ -60,30 +70,24 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
IdleConnTimeout: 90 * time.Second,
}
var tlsAuth, tlsAuthWithCACert bool
if ds.JsonData != nil {
tlsAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
}
if tlsAuth {
transport.TLSClientConfig.InsecureSkipVerify = false
if tlsClientAuth || tlsAuthWithCACert {
decrypted := ds.SecureJsonData.Decrypt()
if tlsAuthWithCACert && len(decrypted["tlsCACert"]) > 0 {
caPool := x509.NewCertPool()
ok := caPool.AppendCertsFromPEM([]byte(decrypted["tlsCACert"]))
if ok {
transport.TLSClientConfig.RootCAs = caPool
if !ok {
return nil, errors.New("Failed to parse TLS CA PEM certificate")
}
transport.TLSClientConfig.RootCAs = caPool
}
cert, err := tls.X509KeyPair([]byte(decrypted["tlsClientCert"]), []byte(decrypted["tlsClientKey"]))
if err != nil {
return nil, err
if tlsClientAuth {
cert, err := tls.X509KeyPair([]byte(decrypted["tlsClientCert"]), []byte(decrypted["tlsClientKey"]))
if err != nil {
return nil, err
}
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
}
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
}
ptc.cache[ds.Id] = cachedTransport{
+116 -31
View File
@@ -29,56 +29,140 @@ func TestDataSourceCache(t *testing.T) {
Convey("Should be using the cached proxy", func() {
So(t2, ShouldEqual, t1)
})
Convey("Should verify TLS by default", func() {
So(t1.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false)
})
Convey("Should have no TLS client certificate configured", func() {
So(len(t1.TLSClientConfig.Certificates), ShouldEqual, 0)
})
Convey("Should have no user-supplied TLS CA onfigured", func() {
So(t1.TLSClientConfig.RootCAs, ShouldBeNil)
})
})
Convey("When getting kubernetes datasource proxy", t, func() {
Convey("When caching a datasource proxy then updating it", t, func() {
clearCache()
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuthWithCACert", true)
tlsCaCert, err := util.Encrypt([]byte(caCert), "password")
So(err, ShouldBeNil)
ds := DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
SecureJsonData: map[string][]byte{"tlsCACert": tlsCaCert},
Updated: time.Now().Add(-2 * time.Minute),
}
t1, err := ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should verify TLS by default", func() {
So(t1.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false)
})
Convey("Should have no TLS client certificate configured", func() {
So(len(t1.TLSClientConfig.Certificates), ShouldEqual, 0)
})
Convey("Should have no user-supplied TLS CA configured", func() {
So(t1.TLSClientConfig.RootCAs, ShouldBeNil)
})
ds.JsonData = nil
ds.SecureJsonData = map[string][]byte{}
ds.Updated = time.Now()
t2, err := ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should have no user-supplied TLS CA configured after the update", func() {
So(t2.TLSClientConfig.RootCAs, ShouldBeNil)
})
})
Convey("When caching a datasource proxy with TLS client authentication enabled", t, func() {
clearCache()
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuth", true)
tlsClientCert, err := util.Encrypt([]byte(clientCert), "password")
So(err, ShouldBeNil)
tlsClientKey, err := util.Encrypt([]byte(clientKey), "password")
So(err, ShouldBeNil)
ds := DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
SecureJsonData: map[string][]byte{
"tlsClientCert": tlsClientCert,
"tlsClientKey": tlsClientKey,
},
}
tr, err := ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should verify TLS by default", func() {
So(tr.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false)
})
Convey("Should have a TLS client certificate configured", func() {
So(len(tr.TLSClientConfig.Certificates), ShouldEqual, 1)
})
})
Convey("When caching a datasource proxy with a user-supplied TLS CA", t, func() {
clearCache()
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuthWithCACert", true)
t := time.Now()
tlsCaCert, err := util.Encrypt([]byte(caCert), "password")
So(err, ShouldBeNil)
ds := DataSource{
Url: "http://k8s:8001",
Type: "Kubernetes",
Updated: t.Add(-2 * time.Minute),
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
SecureJsonData: map[string][]byte{"tlsCACert": tlsCaCert},
}
transport, err := ds.GetHttpTransport()
tr, err := ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should have no cert", func() {
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, true)
Convey("Should verify TLS by default", func() {
So(tr.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false)
})
Convey("Should have a TLS CA configured", func() {
So(len(tr.TLSClientConfig.RootCAs.Subjects()), ShouldEqual, 1)
})
})
ds.JsonData = json
ds.SecureJsonData = map[string][]byte{
"tlsCACert": util.Encrypt([]byte(caCert), "password"),
"tlsClientCert": util.Encrypt([]byte(clientCert), "password"),
"tlsClientKey": util.Encrypt([]byte(clientKey), "password"),
Convey("When caching a datasource proxy when user skips TLS verification", t, func() {
clearCache()
json := simplejson.New()
json.Set("tlsSkipVerify", true)
ds := DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
}
ds.Updated = t.Add(-1 * time.Minute)
transport, err = ds.GetHttpTransport()
tr, err := ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should add cert", func() {
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false)
So(len(transport.TLSClientConfig.Certificates), ShouldEqual, 1)
})
ds.JsonData = nil
ds.SecureJsonData = map[string][]byte{}
ds.Updated = t
transport, err = ds.GetHttpTransport()
So(err, ShouldBeNil)
Convey("Should remove cert", func() {
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, true)
So(len(transport.TLSClientConfig.Certificates), ShouldEqual, 0)
Convey("Should skip TLS verification", func() {
So(tr.TLSClientConfig.InsecureSkipVerify, ShouldEqual, true)
})
})
}
@@ -110,7 +194,8 @@ FHoXIyGOdq1chmRVocdGBCF8fUoGIbuF14r53rpvcbEKtKnnP8+96luKAZLq0a4n
3lb92xM=
-----END CERTIFICATE-----`
const clientCert string = `-----BEGIN CERTIFICATE-----
const clientCert string = `
-----BEGIN CERTIFICATE-----
MIICsjCCAZoCCQCcd8sOfstQLzANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj
YS1rOHMtc3RobG0wHhcNMTYxMTAyMDkyNTE1WhcNMTcxMTAyMDkyNTE1WjAfMR0w
GwYDVQQDDBRhZG0tZGFuaWVsLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQAD
+91
View File
@@ -0,0 +1,91 @@
package models
import (
"errors"
"strings"
"time"
)
// Typed errors
var (
ErrFolderNotFound = errors.New("Folder not found")
ErrFolderVersionMismatch = errors.New("The folder has been changed by someone else")
ErrFolderTitleEmpty = errors.New("Folder title cannot be empty")
ErrFolderWithSameUIDExists = errors.New("A folder/dashboard with the same uid already exists")
ErrFolderSameNameExists = errors.New("A folder or dashboard in the general folder with the same name already exists")
ErrFolderFailedGenerateUniqueUid = errors.New("Failed to generate unique folder id")
ErrFolderAccessDenied = errors.New("Access denied to folder")
)
type Folder struct {
Id int64
Uid string
Title string
Url string
Version int
Created time.Time
Updated time.Time
UpdatedBy int64
CreatedBy int64
HasAcl bool
}
// GetDashboardModel turns the command into the savable model
func (cmd *CreateFolderCommand) GetDashboardModel(orgId int64, userId int64) *Dashboard {
dashFolder := NewDashboardFolder(strings.TrimSpace(cmd.Title))
dashFolder.OrgId = orgId
dashFolder.SetUid(strings.TrimSpace(cmd.Uid))
if userId == 0 {
userId = -1
}
dashFolder.CreatedBy = userId
dashFolder.UpdatedBy = userId
dashFolder.UpdateSlug()
return dashFolder
}
// UpdateDashboardModel updates an existing model from command into model for update
func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard, orgId int64, userId int64) {
dashFolder.OrgId = orgId
dashFolder.Title = strings.TrimSpace(cmd.Title)
dashFolder.Data.Set("title", dashFolder.Title)
if cmd.Uid != "" {
dashFolder.SetUid(cmd.Uid)
}
dashFolder.SetVersion(cmd.Version)
dashFolder.IsFolder = true
if userId == 0 {
userId = -1
}
dashFolder.UpdatedBy = userId
dashFolder.UpdateSlug()
}
//
// COMMANDS
//
type CreateFolderCommand struct {
Uid string `json:"uid"`
Title string `json:"title"`
Result *Folder
}
type UpdateFolderCommand struct {
Uid string `json:"uid"`
Title string `json:"title"`
Version int `json:"version"`
Overwrite bool `json:"overwrite"`
Result *Folder
}
+3
View File
@@ -0,0 +1,3 @@
package models
type GetDBHealthQuery struct{}
+36
View File
@@ -0,0 +1,36 @@
package models
import (
"time"
)
type LoginAttempt struct {
Id int64
Username string
IpAddress string
Created int64
}
// ---------------------
// COMMANDS
type CreateLoginAttemptCommand struct {
Username string
IpAddress string
Result LoginAttempt
}
type DeleteOldLoginAttemptsCommand struct {
OlderThan time.Time
DeletedRows int64
}
// ---------------------
// QUERIES
type GetUserLoginAttemptCountQuery struct {
Username string
Since time.Time
Result int64
}
+1 -1
View File
@@ -7,5 +7,5 @@ const (
GOOGLE
TWITTER
GENERIC
GRAFANANET
GRAFANA_COM
)
+1
View File
@@ -3,6 +3,7 @@ package models
import "errors"
var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
var ErrSmtpNotEnabled = errors.New("SMTP not configured, check your grafana.ini config file's [smtp] section.")
type SendEmailCommand struct {
To []string
+19 -13
View File
@@ -18,25 +18,25 @@ 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 {
if r == ROLE_ADMIN {
return true
}
if r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR {
if r == ROLE_EDITOR {
return other != ROLE_ADMIN
}
return r == other
return false
}
func (r *RoleType) UnmarshalJSON(data []byte) error {
@@ -95,7 +95,10 @@ type UpdateOrgUserCommand struct {
// QUERIES
type GetOrgUsersQuery struct {
OrgId int64
OrgId int64
Query string
Limit int
Result []*OrgUserDTO
}
@@ -103,9 +106,12 @@ type GetOrgUsersQuery struct {
// Projections and DTOs
type OrgUserDTO struct {
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
Login string `json:"login"`
Role string `json:"role"`
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
Login string `json:"login"`
Role string `json:"role"`
LastSeenAt time.Time `json:"lastSeenAt"`
LastSeenAtAge string `json:"lastSeenAtAge"`
}
-6
View File
@@ -1,6 +0,0 @@
package models
type GrafanaServer interface {
Start()
Shutdown(code int, reason string)
}
+18 -14
View File
@@ -1,11 +1,14 @@
package models
type SystemStats struct {
DashboardCount int64
UserCount int64
OrgCount int64
PlaylistCount int64
AlertCount int64
Dashboards int64
Datasources int64
Users int64
ActiveUsers int64
Orgs int64
Playlists int64
Alerts int64
Stars int64
}
type DataSourceStats struct {
@@ -22,15 +25,16 @@ type GetDataSourceStatsQuery struct {
}
type AdminStats struct {
UserCount int `json:"user_count"`
OrgCount int `json:"org_count"`
DashboardCount int `json:"dashboard_count"`
DbSnapshotCount int `json:"db_snapshot_count"`
DbTagCount int `json:"db_tag_count"`
DataSourceCount int `json:"data_source_count"`
PlaylistCount int `json:"playlist_count"`
StarredDbCount int `json:"starred_db_count"`
AlertCount int `json:"alert_count"`
Users int `json:"users"`
Orgs int `json:"orgs"`
Dashboards int `json:"dashboards"`
Snapshots int `json:"snapshots"`
Tags int `json:"tags"`
Datasources int `json:"datasources"`
Playlists int `json:"playlists"`
Stars int `json:"stars"`
Alerts int `json:"alerts"`
ActiveUsers int `json:"activeUsers"`
}
type GetAdminStatsQuery struct {
+60
View File
@@ -0,0 +1,60 @@
package models
import (
"strings"
)
type Tag struct {
Id int64
Key string
Value string
}
func ParseTagPairs(tagPairs []string) (tags []*Tag) {
if tagPairs == nil {
return []*Tag{}
}
for _, tagPair := range tagPairs {
var tag Tag
if strings.Contains(tagPair, ":") {
keyValue := strings.Split(tagPair, ":")
tag.Key = strings.Trim(keyValue[0], " ")
tag.Value = strings.Trim(keyValue[1], " ")
} else {
tag.Key = strings.Trim(tagPair, " ")
}
if tag.Key == "" || ContainsTag(tags, &tag) {
continue
}
tags = append(tags, &tag)
}
return tags
}
func ContainsTag(existingTags []*Tag, tag *Tag) bool {
for _, t := range existingTags {
if t.Key == tag.Key && t.Value == tag.Value {
return true
}
}
return false
}
func JoinTagPairs(tags []*Tag) []string {
tagPairs := []string{}
for _, tag := range tags {
if tag.Value != "" {
tagPairs = append(tagPairs, tag.Key+":"+tag.Value)
} else {
tagPairs = append(tagPairs, tag.Key)
}
}
return tagPairs
}
+95
View File
@@ -0,0 +1,95 @@
package models
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestParsingTags(t *testing.T) {
Convey("Testing parsing a tag pairs into tags", t, func() {
Convey("Can parse one empty tag", func() {
tags := ParseTagPairs([]string{""})
So(len(tags), ShouldEqual, 0)
})
Convey("Can parse valid tags", func() {
tags := ParseTagPairs([]string{"outage", "type:outage", "error"})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "outage")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "type")
So(tags[1].Value, ShouldEqual, "outage")
So(tags[2].Key, ShouldEqual, "error")
So(tags[2].Value, ShouldEqual, "")
})
Convey("Can parse tags with spaces", func() {
tags := ParseTagPairs([]string{" outage ", " type : outage ", "error "})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "outage")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "type")
So(tags[1].Value, ShouldEqual, "outage")
So(tags[2].Key, ShouldEqual, "error")
So(tags[2].Value, ShouldEqual, "")
})
Convey("Can parse empty tags", func() {
tags := ParseTagPairs([]string{" outage ", "", "", ":", "type : outage ", "error ", "", ""})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "outage")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "type")
So(tags[1].Value, ShouldEqual, "outage")
So(tags[2].Key, ShouldEqual, "error")
So(tags[2].Value, ShouldEqual, "")
})
Convey("Can parse tags with extra colons", func() {
tags := ParseTagPairs([]string{" outage", "type : outage:outage2 :outage3 ", "error :"})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "outage")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "type")
So(tags[1].Value, ShouldEqual, "outage")
So(tags[2].Key, ShouldEqual, "error")
So(tags[2].Value, ShouldEqual, "")
})
Convey("Can parse tags that contains key and values with spaces", func() {
tags := ParseTagPairs([]string{" outage 1", "type 1: outage 1 ", "has error "})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "outage 1")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "type 1")
So(tags[1].Value, ShouldEqual, "outage 1")
So(tags[2].Key, ShouldEqual, "has error")
So(tags[2].Value, ShouldEqual, "")
})
Convey("Can filter out duplicate tags", func() {
tags := ParseTagPairs([]string{"test", "test", "key:val1", "key:val2"})
So(len(tags), ShouldEqual, 3)
So(tags[0].Key, ShouldEqual, "test")
So(tags[0].Value, ShouldEqual, "")
So(tags[1].Key, ShouldEqual, "key")
So(tags[1].Value, ShouldEqual, "val1")
So(tags[2].Key, ShouldEqual, "key")
So(tags[2].Value, ShouldEqual, "val2")
})
Convey("Can join tag pairs", func() {
tagPairs := []*Tag{
{Key: "key1", Value: "val1"},
{Key: "key2", Value: ""},
{Key: "key3"},
}
tags := JoinTagPairs(tagPairs)
So(len(tags), ShouldEqual, 3)
So(tags[0], ShouldEqual, "key1:val1")
So(tags[1], ShouldEqual, "key2")
So(tags[2], ShouldEqual, "key3")
})
})
}
+85
View File
@@ -0,0 +1,85 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrTeamNotFound = errors.New("Team not found")
ErrTeamNameTaken = errors.New("Team name is taken")
ErrTeamMemberNotFound = errors.New("Team member not found")
)
// Team model
type Team struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Email string `json:"email"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
// ---------------------
// COMMANDS
type CreateTeamCommand struct {
Name string `json:"name" binding:"Required"`
Email string `json:"email"`
OrgId int64 `json:"-"`
Result Team `json:"-"`
}
type UpdateTeamCommand struct {
Id int64
Name string
Email string
OrgId int64 `json:"-"`
}
type DeleteTeamCommand struct {
OrgId int64
Id int64
}
type GetTeamByIdQuery struct {
OrgId int64
Id int64
Result *Team
}
type GetTeamsByUserQuery struct {
OrgId int64
UserId int64 `json:"userId"`
Result []*Team `json:"teams"`
}
type SearchTeamsQuery struct {
Query string
Name string
Limit int
Page int
OrgId int64
Result SearchTeamQueryResult
}
type SearchTeamDto struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
MemberCount int64 `json:"memberCount"`
}
type SearchTeamQueryResult struct {
TotalCount int64 `json:"totalCount"`
Teams []*SearchTeamDto `json:"teams"`
Page int `json:"page"`
PerPage int `json:"perPage"`
}
+58
View File
@@ -0,0 +1,58 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrTeamMemberAlreadyAdded = errors.New("User is already added to this team")
)
// TeamMember model
type TeamMember struct {
Id int64
OrgId int64
TeamId int64
UserId int64
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type AddTeamMemberCommand struct {
UserId int64 `json:"userId" binding:"Required"`
OrgId int64 `json:"-"`
TeamId int64 `json:"-"`
}
type RemoveTeamMemberCommand struct {
OrgId int64 `json:"-"`
UserId int64
TeamId int64
}
// ----------------------
// QUERIES
type GetTeamMembersQuery struct {
OrgId int64
TeamId int64
Result []*TeamMemberDTO
}
// ----------------------
// Projections and DTOs
type TeamMemberDTO struct {
OrgId int64 `json:"orgId"`
TeamId int64 `json:"teamId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
Login string `json:"login"`
AvatarUrl string `json:"avatarUrl"`
}
+4
View File
@@ -60,6 +60,10 @@ type UpdateTempUserStatusCommand struct {
Status TempUserStatus
}
type UpdateTempUserWithEmailSentCommand struct {
Code string
}
type GetTempUsersQuery struct {
OrgId int64
Email string
+18
View File
@@ -0,0 +1,18 @@
package models
import "time"
type InsertSqlTestDataCommand struct {
}
type SqlTestData struct {
Id int64
Metric1 string
Metric2 string
ValueBigInt int64
ValueDouble float64
ValueFloat float32
ValueInt int
TimeEpoch int64
TimeDateTime time.Time
}
+43 -7
View File
@@ -33,8 +33,9 @@ type User struct {
IsAdmin bool
OrgId int64
Created time.Time
Updated time.Time
Created time.Time
Updated time.Time
LastSeenAt time.Time
}
func (u *User) NameOrFallback() string {
@@ -117,6 +118,7 @@ type GetSignedInUserQuery struct {
UserId int64
Login string
Email string
OrgId int64
Result *SignedInUser
}
@@ -126,6 +128,7 @@ type GetUserProfileQuery struct {
}
type SearchUsersQuery struct {
OrgId int64
Query string
Page int
Limit int
@@ -157,11 +160,41 @@ type SignedInUser struct {
Name string
Email string
ApiKeyId int64
OrgCount int
IsGrafanaAdmin bool
IsAnonymous bool
HelpFlags1 HelpFlags1
LastSeenAt time.Time
}
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
return u.UserId > 0 && time.Since(u.LastSeenAt) > time.Minute*5
}
func (u *SignedInUser) NameOrFallback() string {
if u.Name != "" {
return u.Name
} else if u.Login != "" {
return u.Login
} else {
return u.Email
}
}
type UpdateUserLastSeenAtCommand struct {
UserId int64
}
func (user *SignedInUser) HasRole(role RoleType) bool {
if user.IsGrafanaAdmin {
return true
}
return user.OrgRole.Includes(role)
}
type UserProfileDTO struct {
Id int64 `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Login string `json:"login"`
@@ -171,11 +204,14 @@ type UserProfileDTO struct {
}
type UserSearchHitDTO struct {
Id int64 `json:"id"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
Id int64 `json:"id"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
IsAdmin bool `json:"isAdmin"`
LastSeenAt time.Time `json:"lastSeenAt"`
LastSeenAtAge string `json:"lastSeenAtAge"`
}
type UserIdDTO struct {