diff --git a/.gitignore b/.gitignore index 1c8a89215da..fd04dcd1523 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ public/css/*.min.css conf/custom.ini fig.yml +docker-compose.yml profile.cov /grafana .notouch diff --git a/CHANGELOG.md b/CHANGELOG.md index acefeaec860..c7763166f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * **Snapshots UI**: Dashboard snapshots list can be managed through UI, closes[#1984](https://github.com/grafana/grafana/issues/1984) * **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883) * **Cli**: New cli tool for downloading and updating plugins +* **Annotations**: Annotations can now contain links that can be clicked (you can navigate on to annotation popovers), closes [#1588](https://github.com/grafana/grafana/issues/1588) ### Breaking changes * **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info. diff --git a/docker/build_fig.sh b/docker/create_docker_compose.sh similarity index 97% rename from docker/build_fig.sh rename to docker/create_docker_compose.sh index aca4f68413f..8588c1c474a 100755 --- a/docker/build_fig.sh +++ b/docker/create_docker_compose.sh @@ -7,7 +7,7 @@ template_dir=templates grafana_config_file=conf.tmp grafana_config=config -fig_file=fig.yml +fig_file=docker-compose.yml fig_config=fig if [ "$#" == 0 ]; then diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index ec6e30b35f1..b5c72f3182c 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -122,8 +122,8 @@ To configure Grafana add a configuration file named `custom.ini` to the `conf` folder and override any of the settings defined in `conf/defaults.ini`. -Start Grafana by executing `./grafana-server web`. The `grafana-server` binary needs -the working directory to be the root install directory (where the binary -and the `public` folder is located). +Start Grafana by executing `./bin/grafana-server web`. The `grafana-server` +binary needs the working directory to be the root install directory (where the +binary and the `public` folder is located). diff --git a/pkg/api/api.go b/pkg/api/api.go index c21deae5a59..90d613e46e0 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) { r.Patch("/invites/:code/revoke", wrap(RevokeInvite)) // apps - r.Get("/apps", wrap(GetOrgAppsList)) - r.Get("/apps/:appId/settings", wrap(GetAppSettingsById)) - r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings)) + r.Get("/plugins", wrap(GetPluginList)) + r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById)) + r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting)) }, reqOrgAdmin) // create new org diff --git a/pkg/api/app_settings.go b/pkg/api/app_settings.go deleted file mode 100644 index fd0f1a1eab1..00000000000 --- a/pkg/api/app_settings.go +++ /dev/null @@ -1,59 +0,0 @@ -package api - -import ( - "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/middleware" - m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/plugins" -) - -func GetOrgAppsList(c *middleware.Context) Response { - orgApps, err := plugins.GetOrgAppSettings(c.OrgId) - - if err != nil { - return ApiError(500, "Failed to list of apps", err) - } - - result := make([]*dtos.AppSettings, 0) - for _, app := range plugins.Apps { - orgApp := orgApps[app.Id] - result = append(result, dtos.NewAppSettingsDto(app, orgApp)) - } - - return Json(200, result) -} - -func GetAppSettingsById(c *middleware.Context) Response { - appId := c.Params(":appId") - - if pluginDef, exists := plugins.Apps[appId]; !exists { - return ApiError(404, "PluginId not found, no installed plugin with that id", nil) - } else { - orgApps, err := plugins.GetOrgAppSettings(c.OrgId) - if err != nil { - return ApiError(500, "Failed to get org app settings ", nil) - } - orgApp := orgApps[appId] - - return Json(200, dtos.NewAppSettingsDto(pluginDef, orgApp)) - } -} - -func UpdateAppSettings(c *middleware.Context, cmd m.UpdateAppSettingsCmd) Response { - appId := c.Params(":appId") - - cmd.OrgId = c.OrgId - cmd.AppId = appId - - if _, ok := plugins.Apps[cmd.AppId]; !ok { - return ApiError(404, "App type not installed.", nil) - } - - err := bus.Dispatch(&cmd) - if err != nil { - return ApiError(500, "Failed to update App Plugin", err) - } - - return ApiSuccess("App updated") -} diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index babcf9fddb0..88d22800d65 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -33,6 +33,7 @@ func init() { actionHandlers = map[string]actionHandler{ "GetMetricStatistics": handleGetMetricStatistics, "ListMetrics": handleListMetrics, + "DescribeAlarms": handleDescribeAlarms, "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric, "DescribeAlarmHistory": handleDescribeAlarmHistory, "DescribeInstances": handleDescribeInstances, @@ -142,6 +143,49 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { c.JSON(200, resp) } +func handleDescribeAlarms(req *cwRequest, c *middleware.Context) { + cfg := &aws.Config{ + Region: aws.String(req.Region), + Credentials: getCredentials(req.DataSource.Database), + } + + svc := cloudwatch.New(session.New(cfg), cfg) + + reqParam := &struct { + Parameters struct { + ActionPrefix string `json:"actionPrefix"` + AlarmNamePrefix string `json:"alarmNamePrefix"` + AlarmNames []*string `json:"alarmNames"` + StateValue string `json:"stateValue"` + } `json:"parameters"` + }{} + json.Unmarshal(req.Body, reqParam) + + params := &cloudwatch.DescribeAlarmsInput{ + MaxRecords: aws.Int64(100), + } + if reqParam.Parameters.ActionPrefix != "" { + params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix) + } + if reqParam.Parameters.AlarmNamePrefix != "" { + params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix) + } + if len(reqParam.Parameters.AlarmNames) != 0 { + params.AlarmNames = reqParam.Parameters.AlarmNames + } + if reqParam.Parameters.StateValue != "" { + params.StateValue = aws.String(reqParam.Parameters.StateValue) + } + + resp, err := svc.DescribeAlarms(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { cfg := &aws.Config{ Region: aws.String(req.Region), diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 4d4556c40cb..6e6b4394e2d 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -64,20 +64,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht return &httputil.ReverseProxy{Director: director} } -var dsMap map[int64]*m.DataSource = make(map[int64]*m.DataSource) - func getDatasource(id int64, orgId int64) (*m.DataSource, error) { - // ds, exists := dsMap[id] - // if exists && ds.OrgId == orgId { - // return ds, nil - // } - query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId} if err := bus.Dispatch(&query); err != nil { return nil, err } - dsMap[id] = &query.Result return &query.Result, nil } diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go deleted file mode 100644 index ba26e3eb617..00000000000 --- a/pkg/api/dtos/apps.go +++ /dev/null @@ -1,40 +0,0 @@ -package dtos - -import ( - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/plugins" -) - -type AppSettings struct { - Name string `json:"name"` - AppId string `json:"appId"` - Enabled bool `json:"enabled"` - Pinned bool `json:"pinned"` - Module string `json:"module"` - BaseUrl string `json:"baseUrl"` - Info *plugins.PluginInfo `json:"info"` - Pages []*plugins.AppPluginPage `json:"pages"` - Includes []*plugins.AppIncludeInfo `json:"includes"` - JsonData map[string]interface{} `json:"jsonData"` -} - -func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings { - dto := &AppSettings{ - AppId: def.Id, - Name: def.Name, - Info: &def.Info, - Module: def.Module, - BaseUrl: def.BaseUrl, - Pages: def.Pages, - Includes: def.Includes, - } - - if data != nil { - dto.Enabled = data.Enabled - dto.Pinned = data.Pinned - dto.Info = &def.Info - dto.JsonData = data.JsonData - } - - return dto -} diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 7c28d734be4..21201d30adf 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -16,9 +16,10 @@ type PluginCss struct { } type NavLink struct { - Text string `json:"text"` - Icon string `json:"icon"` - Img string `json:"img"` - Url string `json:"url"` - Children []*NavLink `json:"children"` + Text string `json:"text,omitempty"` + Icon string `json:"icon,omitempty"` + Img string `json:"img,omitempty"` + Url string `json:"url,omitempty"` + Divider bool `json:"divider,omitempty"` + Children []*NavLink `json:"children,omitempty"` } diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go new file mode 100644 index 00000000000..af96202222f --- /dev/null +++ b/pkg/api/dtos/plugins.go @@ -0,0 +1,26 @@ +package dtos + +import "github.com/grafana/grafana/pkg/plugins" + +type PluginSetting struct { + Name string `json:"name"` + Type string `json:"type"` + PluginId string `json:"pluginId"` + Enabled bool `json:"enabled"` + Pinned bool `json:"pinned"` + Module string `json:"module"` + BaseUrl string `json:"baseUrl"` + Info *plugins.PluginInfo `json:"info"` + Pages []*plugins.AppPluginPage `json:"pages"` + Includes []*plugins.AppIncludeInfo `json:"includes"` + JsonData map[string]interface{} `json:"jsonData"` +} + +type PluginListItem struct { + Name string `json:"name"` + Type string `json:"type"` + PluginId string `json:"pluginId"` + Enabled bool `json:"enabled"` + Pinned bool `json:"pinned"` + Info *plugins.PluginInfo `json:"info"` +} diff --git a/pkg/api/index.go b/pkg/api/index.go index 72277fe301b..df752109530 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -53,15 +53,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { Icon: "icon-gf icon-gf-dashboard", Url: setting.AppSubUrl + "/", Children: []*dtos.NavLink{ - {Text: "Home dashboard", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/"}, - {Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"}, - {Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"}, + {Text: "Home", Url: setting.AppSubUrl + "/"}, + {Text: "Playlists", Url: setting.AppSubUrl + "/playlists"}, + {Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots"}, + {Divider: true}, + {Text: "New", Url: setting.AppSubUrl + "/dashboard/new"}, + {Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"}, }, }) - // data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"}) - // data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"}) - if c.OrgRole == m.ROLE_ADMIN { data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ Text: "Data Sources", @@ -72,7 +72,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ Text: "Plugins", Icon: "icon-gf icon-gf-apps", - Url: setting.AppSubUrl + "/apps", + Url: setting.AppSubUrl + "/plugins", }) } diff --git a/pkg/api/plugin_setting.go b/pkg/api/plugin_setting.go new file mode 100644 index 00000000000..08fc5296434 --- /dev/null +++ b/pkg/api/plugin_setting.go @@ -0,0 +1,88 @@ +package api + +import ( + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" +) + +func GetPluginList(c *middleware.Context) Response { + pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId) + + if err != nil { + return ApiError(500, "Failed to get list of plugins", err) + } + + result := make([]*dtos.PluginListItem, 0) + for _, pluginDef := range plugins.Plugins { + listItem := &dtos.PluginListItem{ + PluginId: pluginDef.Id, + Name: pluginDef.Name, + Type: pluginDef.Type, + Info: &pluginDef.Info, + } + + if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists { + listItem.Enabled = pluginSetting.Enabled + listItem.Pinned = pluginSetting.Pinned + } + + result = append(result, listItem) + } + + return Json(200, result) +} + +func GetPluginSettingById(c *middleware.Context) Response { + pluginId := c.Params(":pluginId") + + if def, exists := plugins.Plugins[pluginId]; !exists { + return ApiError(404, "Plugin not found, no installed plugin with that id", nil) + } else { + dto := &dtos.PluginSetting{ + Type: def.Type, + PluginId: def.Id, + Name: def.Name, + Info: &def.Info, + } + + if app, exists := plugins.Apps[pluginId]; exists { + dto.Pages = app.Pages + dto.Includes = app.Includes + dto.BaseUrl = app.BaseUrl + dto.Module = app.Module + } + + query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId} + if err := bus.Dispatch(&query); err != nil { + if err != m.ErrPluginSettingNotFound { + return ApiError(500, "Failed to get login settings", nil) + } + } else { + dto.Enabled = query.Result.Enabled + dto.Pinned = query.Result.Pinned + dto.JsonData = query.Result.JsonData + } + + return Json(200, dto) + } +} + +func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Response { + pluginId := c.Params(":pluginId") + + cmd.OrgId = c.OrgId + cmd.PluginId = pluginId + + if _, ok := plugins.Apps[cmd.PluginId]; !ok { + return ApiError(404, "Plugin not installed.", nil) + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to update plugin setting", err) + } + + return ApiSuccess("Plugin settings updated") +} diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index 55aa0c013ce..92d07988b64 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -26,7 +26,7 @@ type templateData struct { func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) { result := http.Header{} - query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId} + query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId} if err := bus.Dispatch(&query); err != nil { return nil, err diff --git a/pkg/api/pluginproxy/pluginproxy_test.go b/pkg/api/pluginproxy/pluginproxy_test.go index 0b6e9523ffd..f14ca6975e6 100644 --- a/pkg/api/pluginproxy/pluginproxy_test.go +++ b/pkg/api/pluginproxy/pluginproxy_test.go @@ -22,8 +22,8 @@ func TestPluginProxy(t *testing.T) { setting.SecretKey = "password" - bus.AddHandler("test", func(query *m.GetAppSettingByAppIdQuery) error { - query.Result = &m.AppSettings{ + bus.AddHandler("test", func(query *m.GetPluginSettingByIdQuery) error { + query.Result = &m.PluginSetting{ SecureJsonData: map[string][]byte{ "key": util.Encrypt([]byte("123"), "password"), }, diff --git a/pkg/models/app_settings.go b/pkg/models/plugin_setting.go similarity index 71% rename from pkg/models/app_settings.go rename to pkg/models/plugin_setting.go index 6a7bbde694d..777f4599241 100644 --- a/pkg/models/app_settings.go +++ b/pkg/models/plugin_setting.go @@ -9,12 +9,12 @@ import ( ) var ( - ErrAppSettingNotFound = errors.New("AppSetting not found") + ErrPluginSettingNotFound = errors.New("Plugin setting not found") ) -type AppSettings struct { +type PluginSetting struct { Id int64 - AppId string + PluginId string OrgId int64 Enabled bool Pinned bool @@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string { // COMMANDS // Also acts as api DTO -type UpdateAppSettingsCmd struct { +type UpdatePluginSettingCmd struct { Enabled bool `json:"enabled"` Pinned bool `json:"pinned"` JsonData map[string]interface{} `json:"jsonData"` SecureJsonData map[string]string `json:"secureJsonData"` - AppId string `json:"-"` - OrgId int64 `json:"-"` + PluginId string `json:"-"` + OrgId int64 `json:"-"` } -func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData { +func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData { encrypted := make(SecureJsonData) for key, data := range cmd.SecureJsonData { encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey) @@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData { // --------------------- // QUERIES -type GetAppSettingsQuery struct { +type GetPluginSettingsQuery struct { OrgId int64 - Result []*AppSettings + Result []*PluginSetting } -type GetAppSettingByAppIdQuery struct { - AppId string - OrgId int64 - Result *AppSettings +type GetPluginSettingByIdQuery struct { + PluginId string + OrgId int64 + Result *PluginSetting } diff --git a/pkg/plugins/frontend_plugin.go b/pkg/plugins/frontend_plugin.go index a1bcd0c68b5..5acb9966495 100644 --- a/pkg/plugins/frontend_plugin.go +++ b/pkg/plugins/frontend_plugin.go @@ -56,6 +56,10 @@ func (fp *FrontendPluginBase) handleModuleDefaults() { } func evalRelativePluginUrlPath(pathStr string, pluginId string) string { + if pathStr == "" { + return "" + } + u, _ := url.Parse(pathStr) if u.IsAbs() { return pathStr diff --git a/pkg/plugins/queries.go b/pkg/plugins/queries.go index 8e628c5024e..b3cc92bf7d1 100644 --- a/pkg/plugins/queries.go +++ b/pkg/plugins/queries.go @@ -5,44 +5,40 @@ import ( m "github.com/grafana/grafana/pkg/models" ) -func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) { - query := m.GetAppSettingsQuery{OrgId: orgId} +func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) { + query := m.GetPluginSettingsQuery{OrgId: orgId} if err := bus.Dispatch(&query); err != nil { return nil, err } - orgAppsMap := make(map[string]*m.AppSettings) - for _, orgApp := range query.Result { - orgAppsMap[orgApp.AppId] = orgApp + pluginMap := make(map[string]*m.PluginSetting) + for _, plug := range query.Result { + pluginMap[plug.PluginId] = plug } - return orgAppsMap, nil + return pluginMap, nil } func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { enabledPlugins := NewEnabledPlugins() - orgApps, err := GetOrgAppSettings(orgId) + orgPlugins, err := GetPluginSettings(orgId) if err != nil { return nil, err } enabledApps := make(map[string]bool) - for appId, installedApp := range Apps { - var app AppPlugin - app = *installedApp + for pluginId, app := range Apps { - // check if the app is stored in the DB for this org and if so, use the - // state stored there. - if b, ok := orgApps[appId]; ok { + if b, ok := orgPlugins[pluginId]; ok { app.Enabled = b.Enabled app.Pinned = b.Pinned } if app.Enabled { - enabledApps[app.Id] = true - enabledPlugins.Apps = append(enabledPlugins.Apps, &app) + enabledApps[pluginId] = true + enabledPlugins.Apps = append(enabledPlugins.Apps, app) } } diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go deleted file mode 100644 index e7d8a90a495..00000000000 --- a/pkg/services/sqlstore/app_settings.go +++ /dev/null @@ -1,70 +0,0 @@ -package sqlstore - -import ( - "time" - - "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" -) - -func init() { - bus.AddHandler("sql", GetAppSettings) - bus.AddHandler("sql", GetAppSettingByAppId) - bus.AddHandler("sql", UpdateAppSettings) -} - -func GetAppSettings(query *m.GetAppSettingsQuery) error { - sess := x.Where("org_id=?", query.OrgId) - - query.Result = make([]*m.AppSettings, 0) - return sess.Find(&query.Result) -} - -func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error { - appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId} - has, err := x.Get(&appSetting) - if err != nil { - return err - } else if has == false { - return m.ErrAppSettingNotFound - } - query.Result = &appSetting - return nil -} - -func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { - return inTransaction2(func(sess *session) error { - var app m.AppSettings - - exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app) - sess.UseBool("enabled") - sess.UseBool("pinned") - if !exists { - app = m.AppSettings{ - AppId: cmd.AppId, - OrgId: cmd.OrgId, - Enabled: cmd.Enabled, - Pinned: cmd.Pinned, - JsonData: cmd.JsonData, - SecureJsonData: cmd.GetEncryptedJsonData(), - Created: time.Now(), - Updated: time.Now(), - } - _, err = sess.Insert(&app) - return err - } else { - for key, data := range cmd.SecureJsonData { - app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) - } - app.SecureJsonData = cmd.GetEncryptedJsonData() - app.Updated = time.Now() - app.Enabled = cmd.Enabled - app.JsonData = cmd.JsonData - app.Pinned = cmd.Pinned - _, err = sess.Id(app.Id).Update(&app) - return err - } - }) -} diff --git a/pkg/services/sqlstore/migrations/app_settings.go b/pkg/services/sqlstore/migrations/plugin_setting.go similarity index 63% rename from pkg/services/sqlstore/migrations/app_settings.go rename to pkg/services/sqlstore/migrations/plugin_setting.go index 885dbbf9f05..4a8729691d3 100644 --- a/pkg/services/sqlstore/migrations/app_settings.go +++ b/pkg/services/sqlstore/migrations/plugin_setting.go @@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" func addAppSettingsMigration(mg *Migrator) { - appSettingsV2 := Table{ - Name: "app_settings", + pluginSettingTable := Table{ + Name: "plugin_setting", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: true}, - {Name: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "enabled", Type: DB_Bool, Nullable: false}, {Name: "pinned", Type: DB_Bool, Nullable: false}, {Name: "json_data", Type: DB_Text, Nullable: true}, @@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) { {Name: "updated", Type: DB_DateTime, Nullable: false}, }, Indices: []*Index{ - {Cols: []string{"org_id", "app_id"}, Type: UniqueIndex}, + {Cols: []string{"org_id", "plugin_id"}, Type: UniqueIndex}, }, } - mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings")) - - mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2)) + mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable)) //------- indexes ------------------ - addTableIndicesMigrations(mg, "v3", appSettingsV2) + addTableIndicesMigrations(mg, "v1", pluginSettingTable) } diff --git a/pkg/services/sqlstore/plugin_setting.go b/pkg/services/sqlstore/plugin_setting.go new file mode 100644 index 00000000000..53f4857d9b6 --- /dev/null +++ b/pkg/services/sqlstore/plugin_setting.go @@ -0,0 +1,70 @@ +package sqlstore + +import ( + "time" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" +) + +func init() { + bus.AddHandler("sql", GetPluginSettings) + bus.AddHandler("sql", GetPluginSettingById) + bus.AddHandler("sql", UpdatePluginSetting) +} + +func GetPluginSettings(query *m.GetPluginSettingsQuery) error { + sess := x.Where("org_id=?", query.OrgId) + + query.Result = make([]*m.PluginSetting, 0) + return sess.Find(&query.Result) +} + +func GetPluginSettingById(query *m.GetPluginSettingByIdQuery) error { + pluginSetting := m.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId} + has, err := x.Get(&pluginSetting) + if err != nil { + return err + } else if has == false { + return m.ErrPluginSettingNotFound + } + query.Result = &pluginSetting + return nil +} + +func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error { + return inTransaction2(func(sess *session) error { + var pluginSetting m.PluginSetting + + exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting) + sess.UseBool("enabled") + sess.UseBool("pinned") + if !exists { + pluginSetting = m.PluginSetting{ + PluginId: cmd.PluginId, + OrgId: cmd.OrgId, + Enabled: cmd.Enabled, + Pinned: cmd.Pinned, + JsonData: cmd.JsonData, + SecureJsonData: cmd.GetEncryptedJsonData(), + Created: time.Now(), + Updated: time.Now(), + } + _, err = sess.Insert(&pluginSetting) + return err + } else { + for key, data := range cmd.SecureJsonData { + pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) + } + pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData() + pluginSetting.Updated = time.Now() + pluginSetting.Enabled = cmd.Enabled + pluginSetting.JsonData = cmd.JsonData + pluginSetting.Pinned = cmd.Pinned + _, err = sess.Id(pluginSetting.Id).Update(&pluginSetting) + return err + } + }) +} diff --git a/public/app/core/components/colorpicker/colorpicker.ts b/public/app/core/components/colorpicker/colorpicker.ts new file mode 100644 index 00000000000..9c879b867ff --- /dev/null +++ b/public/app/core/components/colorpicker/colorpicker.ts @@ -0,0 +1,81 @@ +/// + +import config from 'app/core/config'; +import _ from 'lodash'; +import $ from 'jquery'; +import coreModule from 'app/core/core_module'; + +var template = ` +
+ + + + +
+ + + +
+ +

+   +

+ +
+`; + +export class ColorPickerCtrl { + colors: any; + autoClose: boolean; + series: any; + showAxisControls: boolean; + + /** @ngInject */ + constructor(private $scope, private $rootScope) { + this.colors = $rootScope.colors; + this.autoClose = $scope.autoClose; + this.series = $scope.series; + } + + toggleAxis(yaxis) { + this.$scope.toggleAxis(); + + if (!this.$scope.autoClose) { + this.$scope.dismiss(); + } + } + + colorSelected(color) { + this.$scope.colorSelected(color); + if (!this.$scope.autoClose) { + this.$scope.dismiss(); + } + } + + close() { + this.$scope.dismiss(); + } +} + +export function colorPicker() { + return { + restrict: 'E', + controller: ColorPickerCtrl, + bindToController: true, + controllerAs: 'ctrl', + template: template, + }; +} + +coreModule.directive('gfColorPicker', colorPicker); diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 96c609018c0..0a2e49e5d72 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -77,8 +77,8 @@ export class GrafanaCtrl { }); }; - $rootScope.performance.scopeCount = scopes; f(root); + $rootScope.performance.scopeCount = scopes; return count; }; diff --git a/public/app/core/components/popover/popover.ts b/public/app/core/components/popover/popover.ts new file mode 100644 index 00000000000..f2a413aca02 --- /dev/null +++ b/public/app/core/components/popover/popover.ts @@ -0,0 +1,55 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import coreModule from '../../core_module'; +import Drop from 'tether-drop'; + +export function popoverDirective() { + return { + restrict: 'E', + transclude: true, + link: function(scope, elem, attrs, ctrl, transclude) { + var inputElem = elem.prev(); + if (inputElem.length === 0) { + console.log('Failed to find input element for popover'); + return; + } + + var offset = attrs.offset || '0 -10px'; + + transclude(function(clone, newScope) { + var content = document.createElement("div"); + _.each(clone, (node) => { + content.appendChild(node); + }); + + var drop = new Drop({ + target: inputElem[0], + content: content, + position: 'right middle', + classes: 'drop-help', + openOn: 'click', + tetherOptions: { + offset: offset + } + }); + + // inputElem.on('focus.popover', function() { + // drop.open(); + // }); + // + // inputElem.on('blur.popover', function() { + // close(); + // }); + + scope.$on('$destroy', function() { + drop.destroy(); + }); + + }); + } + }; +} + +coreModule.directive('gfPopover', popoverDirective); diff --git a/public/app/core/components/sidemenu/sidemenu.html b/public/app/core/components/sidemenu/sidemenu.html index 6e460e813ce..7fa5cc56647 100644 --- a/public/app/core/components/sidemenu/sidemenu.html +++ b/public/app/core/components/sidemenu/sidemenu.html @@ -1,53 +1,51 @@