diff --git a/pkg/api/api.go b/pkg/api/api.go index 2155978fc81..ec256bb5c52 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -41,8 +41,8 @@ func Register(r *macaron.Macaron) { r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) - r.Get("/plugins", reqSignedIn, Index) - r.Get("/plugins/edit/*", reqSignedIn, Index) + r.Get("/apps", reqSignedIn, Index) + r.Get("/apps/edit/*", reqSignedIn, Index) r.Get("/dashboard/*", reqSignedIn, Index) r.Get("/dashboard-solo/*", reqSignedIn, Index) @@ -120,6 +120,11 @@ func Register(r *macaron.Macaron) { r.Get("/invites", wrap(GetPendingOrgInvites)) r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite)) 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)) }, reqOrgAdmin) // create new org @@ -205,5 +210,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) + InitApiPluginRoutes(r) + r.NotFound(NotFoundHandler) } diff --git a/pkg/api/api_plugin.go b/pkg/api/api_plugin.go new file mode 100644 index 00000000000..a368e9f1073 --- /dev/null +++ b/pkg/api/api_plugin.go @@ -0,0 +1,75 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/util" +) + +func InitApiPluginRoutes(r *macaron.Macaron) { + for _, plugin := range plugins.ApiPlugins { + log.Info("Plugin: Adding proxy routes for api plugin") + for _, route := range plugin.Routes { + url := util.JoinUrlFragments("/api/plugin-proxy/", route.Path) + handlers := make([]macaron.Handler, 0) + if route.ReqSignedIn { + handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})) + } + if route.ReqGrafanaAdmin { + handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})) + } + if route.ReqSignedIn && route.ReqRole != "" { + if route.ReqRole == m.ROLE_ADMIN { + handlers = append(handlers, middleware.RoleAuth(m.ROLE_ADMIN)) + } else if route.ReqRole == m.ROLE_EDITOR { + handlers = append(handlers, middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) + } + } + handlers = append(handlers, ApiPlugin(route.Url)) + r.Route(url, route.Method, handlers...) + log.Info("Plugin: Adding route %s", url) + } + } +} + +func ApiPlugin(routeUrl string) macaron.Handler { + return func(c *middleware.Context) { + path := c.Params("*") + + //Create a HTTP header with the context in it. + ctx, err := json.Marshal(c.SignedInUser) + if err != nil { + c.JsonApiErr(500, "failed to marshal context to json.", err) + return + } + targetUrl, _ := url.Parse(routeUrl) + proxy := NewApiPluginProxy(string(ctx), path, targetUrl) + proxy.Transport = dataProxyTransport + proxy.ServeHTTP(c.RW(), c.Req.Request) + } +} + +func NewApiPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { + director := func(req *http.Request) { + req.URL.Scheme = targetUrl.Scheme + req.URL.Host = targetUrl.Host + req.Host = targetUrl.Host + + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) + + // clear cookie headers + req.Header.Del("Cookie") + req.Header.Del("Set-Cookie") + req.Header.Add("Grafana-Context", ctx) + } + + return &httputil.ReverseProxy{Director: director} +} diff --git a/pkg/api/app_settings.go b/pkg/api/app_settings.go new file mode 100644 index 00000000000..fd0f1a1eab1 --- /dev/null +++ b/pkg/api/app_settings.go @@ -0,0 +1,59 @@ +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/datasources.go b/pkg/api/datasources.go index bef24d61d24..54959840d03 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -3,6 +3,7 @@ package api import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + //"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" @@ -115,13 +116,19 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) { } func GetDataSourcePlugins(c *middleware.Context) { - dsList := make(map[string]interface{}) + dsList := make(map[string]*plugins.DataSourcePlugin) - for key, value := range plugins.DataSources { - if !value.BuiltIn { - dsList[key] = value + if enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId); err != nil { + c.JsonApiErr(500, "Failed to get org apps", err) + return + } else { + + for key, value := range enabledPlugins.DataSources { + if !value.BuiltIn { + dsList[key] = value + } } - } - c.JSON(200, dsList) + c.JSON(200, dsList) + } } diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go new file mode 100644 index 00000000000..c520b6921a1 --- /dev/null +++ b/pkg/api/dtos/apps.go @@ -0,0 +1,33 @@ +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"` + Info *plugins.PluginInfo `json:"info"` + Pages []*plugins.AppPluginPage `json:"pages"` + 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, + Pages: def.Pages, + } + + if data != nil { + dto.Enabled = data.Enabled + dto.Pinned = data.Pinned + dto.Info = &def.Info + } + + return dto +} diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 1314d2d94ac..c5b81ee0e98 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -8,9 +8,9 @@ type IndexViewData struct { GoogleAnalyticsId string GoogleTagManagerId string - PluginCss []*PluginCss - PluginJs []string - MainNavLinks []*NavLink + PluginCss []*PluginCss + PluginModules []string + MainNavLinks []*NavLink } type PluginCss struct { @@ -21,5 +21,6 @@ type PluginCss struct { type NavLink struct { Text string `json:"text"` Icon string `json:"icon"` - Href string `json:"href"` + Img string `json:"img"` + Url string `json:"url"` } diff --git a/pkg/api/dtos/plugin_bundle.go b/pkg/api/dtos/plugin_bundle.go deleted file mode 100644 index f043da39904..00000000000 --- a/pkg/api/dtos/plugin_bundle.go +++ /dev/null @@ -1,8 +0,0 @@ -package dtos - -type PluginBundle struct { - Type string `json:"type"` - Enabled bool `json:"enabled"` - Module string `json:"module"` - JsonData map[string]interface{} `json:"jsonData"` -} diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 5cba3258122..0f9cdee02a9 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -29,6 +29,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro datasources := make(map[string]interface{}) var defaultDatasource string + enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) + if err != nil { + return nil, err + } + for _, ds := range orgDataSources { url := ds.Url @@ -42,7 +47,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro "url": url, } - meta, exists := plugins.DataSources[ds.Type] + meta, exists := enabledPlugins.DataSources[ds.Type] if !exists { log.Error(3, "Could not find plugin definition for data source: %v", ds.Type) continue @@ -110,8 +115,8 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } panels := map[string]interface{}{} - for _, panel := range plugins.Panels { - panels[panel.Type] = map[string]interface{}{ + for _, panel := range enabledPlugins.Panels { + panels[panel.Id] = map[string]interface{}{ "module": panel.Module, "name": panel.Name, } diff --git a/pkg/api/index.go b/pkg/api/index.go index ff5a923ebba..df885fdb2f4 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -4,6 +4,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" ) @@ -50,7 +51,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ Text: "Dashboards", Icon: "fa fa-fw fa-th-large", - Href: "/", + Url: "/", }) data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ @@ -63,8 +64,37 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ Text: "Data Sources", Icon: "fa fa-fw fa-database", - Href: "/datasources", + Url: "/datasources", }) + + data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ + Text: "Apps", + Icon: "fa fa-fw fa-cubes", + Url: "/apps", + }) + } + + enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) + if err != nil { + return nil, err + } + + for _, plugin := range enabledPlugins.Apps { + if plugin.Module != "" { + data.PluginModules = append(data.PluginModules, plugin.Module) + } + + if plugin.Css != nil { + data.PluginCss = append(data.PluginCss, &dtos.PluginCss{Light: plugin.Css.Light, Dark: plugin.Css.Dark}) + } + + if plugin.Pinned { + data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ + Text: plugin.Name, + Url: "/apps/edit/" + plugin.Id, + Img: plugin.Info.Logos.Small, + }) + } } return &data, nil diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index 1debde98a0e..ff7f8a053bc 100644 --- a/pkg/cmd/web.go +++ b/pkg/cmd/web.go @@ -30,9 +30,9 @@ func newMacaron() *macaron.Macaron { } for _, route := range plugins.StaticRoutes { - pluginRoute := path.Join("/public/plugins/", route.Url) - log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Path) - mapStatic(m, route.Path, "", pluginRoute) + pluginRoute := path.Join("/public/plugins/", route.PluginId) + log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Directory) + mapStatic(m, route.Directory, "", pluginRoute) } mapStatic(m, setting.StaticRootPath, "", "public") diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 8704ec5a787..9ad417606f5 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -253,3 +253,7 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) { ctx.JSON(status, resp) } + +func (ctx *Context) HasUserRole(role m.RoleType) bool { + return ctx.OrgRole.Includes(role) +} diff --git a/pkg/models/plugin_bundle.go b/pkg/models/app_settings.go similarity index 59% rename from pkg/models/plugin_bundle.go rename to pkg/models/app_settings.go index 5f4e508b9b2..558946d0277 100644 --- a/pkg/models/plugin_bundle.go +++ b/pkg/models/app_settings.go @@ -2,11 +2,12 @@ package models import "time" -type PluginBundle struct { +type AppSettings struct { Id int64 - Type string + AppId string OrgId int64 Enabled bool + Pinned bool JsonData map[string]interface{} Created time.Time @@ -17,18 +18,18 @@ type PluginBundle struct { // COMMANDS // Also acts as api DTO -type UpdatePluginBundleCmd struct { - Type string `json:"type" binding:"Required"` +type UpdateAppSettingsCmd struct { Enabled bool `json:"enabled"` + Pinned bool `json:"pinned"` JsonData map[string]interface{} `json:"jsonData"` - Id int64 `json:"-"` - OrgId int64 `json:"-"` + AppId string `json:"-"` + OrgId int64 `json:"-"` } // --------------------- // QUERIES -type GetPluginBundlesQuery struct { +type GetAppSettingsQuery struct { OrgId int64 - Result []*PluginBundle + Result []*AppSettings } diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index f456fa95373..48d17deb9db 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -26,6 +26,17 @@ func (r RoleType) IsValid() bool { return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR } +func (r RoleType) Includes(other RoleType) bool { + if r == ROLE_ADMIN { + return true + } + if r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR { + return other != ROLE_ADMIN + } + + return r == other +} + type OrgUser struct { Id int64 OrgId int64 diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go new file mode 100644 index 00000000000..9580f0024f4 --- /dev/null +++ b/pkg/plugins/app_plugin.go @@ -0,0 +1,43 @@ +package plugins + +import ( + "encoding/json" + + "github.com/grafana/grafana/pkg/models" +) + +type AppPluginPage struct { + Name string `json:"name"` + Url string `json:"url"` + ReqRole models.RoleType `json:"reqRole"` +} + +type AppPluginCss struct { + Light string `json:"light"` + Dark string `json:"dark"` +} + +type AppPlugin struct { + FrontendPluginBase + Css *AppPluginCss `json:"css"` + Pages []*AppPluginPage `json:"pages"` + + Pinned bool `json:"-"` + Enabled bool `json:"-"` +} + +func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&app); err != nil { + return err + } + + if app.Css != nil { + app.Css.Dark = evalRelativePluginUrlPath(app.Css.Dark, app.Id) + app.Css.Light = evalRelativePluginUrlPath(app.Css.Light, app.Id) + } + + app.PluginDir = pluginDir + app.initFrontendPlugin() + Apps[app.Id] = app + return nil +} diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go new file mode 100644 index 00000000000..e1bb9047297 --- /dev/null +++ b/pkg/plugins/datasource_plugin.go @@ -0,0 +1,25 @@ +package plugins + +import "encoding/json" + +type DataSourcePlugin struct { + FrontendPluginBase + DefaultMatchFormat string `json:"defaultMatchFormat"` + Annotations bool `json:"annotations"` + Metrics bool `json:"metrics"` + BuiltIn bool `json:"builtIn"` + Mixed bool `json:"mixed"` + App string `json:"app"` +} + +func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&p); err != nil { + return err + } + + p.PluginDir = pluginDir + p.initFrontendPlugin() + DataSources[p.Id] = p + + return nil +} diff --git a/pkg/plugins/frontend_plugin.go b/pkg/plugins/frontend_plugin.go new file mode 100644 index 00000000000..0f0d1676066 --- /dev/null +++ b/pkg/plugins/frontend_plugin.go @@ -0,0 +1,48 @@ +package plugins + +import ( + "net/url" + "path" + "path/filepath" +) + +type FrontendPluginBase struct { + PluginBase + Module string `json:"module"` + StaticRoot string `json:"staticRoot"` +} + +func (fp *FrontendPluginBase) initFrontendPlugin() { + if fp.StaticRoot != "" { + StaticRoutes = append(StaticRoutes, &PluginStaticRoute{ + Directory: filepath.Join(fp.PluginDir, fp.StaticRoot), + PluginId: fp.Id, + }) + } + + fp.Info.Logos.Small = evalRelativePluginUrlPath(fp.Info.Logos.Small, fp.Id) + fp.Info.Logos.Large = evalRelativePluginUrlPath(fp.Info.Logos.Large, fp.Id) + + fp.handleModuleDefaults() +} + +func (fp *FrontendPluginBase) handleModuleDefaults() { + if fp.Module != "" { + return + } + + if fp.StaticRoot != "" { + fp.Module = path.Join("plugins", fp.Id, "module") + return + } + + fp.Module = path.Join("app/plugins", fp.Type, fp.Id, "module") +} + +func evalRelativePluginUrlPath(pathStr string, pluginId string) string { + u, _ := url.Parse(pathStr) + if u.IsAbs() { + return pathStr + } + return path.Join("public/plugins", pluginId, pathStr) +} diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 889229e79b9..65bd0ef1f2d 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -1,26 +1,74 @@ package plugins -type DataSourcePlugin struct { - Type string `json:"type"` - Name string `json:"name"` - ServiceName string `json:"serviceName"` - Module string `json:"module"` - Partials map[string]interface{} `json:"partials"` - DefaultMatchFormat string `json:"defaultMatchFormat"` - Annotations bool `json:"annotations"` - Metrics bool `json:"metrics"` - BuiltIn bool `json:"builtIn"` - StaticRootConfig *StaticRootConfig `json:"staticRoot"` +import ( + "encoding/json" + + "github.com/grafana/grafana/pkg/models" +) + +type PluginLoader interface { + Load(decoder *json.Decoder, pluginDir string) error } -type PanelPlugin struct { - Type string `json:"type"` - Name string `json:"name"` - Module string `json:"module"` - StaticRootConfig *StaticRootConfig `json:"staticRoot"` +type PluginBase struct { + Type string `json:"type"` + Name string `json:"name"` + Id string `json:"id"` + App string `json:"app"` + Info PluginInfo `json:"info"` + PluginDir string `json:"-"` } -type StaticRootConfig struct { +type PluginInfo struct { + Author PluginInfoLink `json:"author"` + Description string `json:"description"` + Links []PluginInfoLink `json:"links"` + Logos PluginLogos `json:"logos"` + Version string `json:"version"` + Updated string `json:"updated"` +} + +type PluginInfoLink struct { + Name string `json:"name"` Url string `json:"url"` - Path string `json:"path"` +} + +type PluginLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +type PluginStaticRoute struct { + Directory string + PluginId string +} + +type ApiPluginRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"reqSignedIn"` + ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` + ReqRole models.RoleType `json:"reqRole"` + Url string `json:"url"` +} + +type ApiPlugin struct { + PluginBase + Routes []*ApiPluginRoute `json:"routes"` +} + +type EnabledPlugins struct { + Panels []*PanelPlugin + DataSources map[string]*DataSourcePlugin + ApiList []*ApiPlugin + Apps []*AppPlugin +} + +func NewEnabledPlugins() EnabledPlugins { + return EnabledPlugins{ + Panels: make([]*PanelPlugin, 0), + DataSources: make(map[string]*DataSourcePlugin), + ApiList: make([]*ApiPlugin, 0), + Apps: make([]*AppPlugin, 0), + } } diff --git a/pkg/plugins/panel_plugin.go b/pkg/plugins/panel_plugin.go new file mode 100644 index 00000000000..5b99ac52344 --- /dev/null +++ b/pkg/plugins/panel_plugin.go @@ -0,0 +1,19 @@ +package plugins + +import "encoding/json" + +type PanelPlugin struct { + FrontendPluginBase +} + +func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&p); err != nil { + return err + } + + p.PluginDir = pluginDir + p.initFrontendPlugin() + Panels[p.Id] = p + + return nil +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 23b927e5ea9..1b945141f6a 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -1,12 +1,16 @@ package plugins import ( + "bytes" "encoding/json" "errors" + "io" "os" "path" "path/filepath" + "reflect" "strings" + "text/template" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" @@ -14,9 +18,12 @@ import ( ) var ( - DataSources map[string]DataSourcePlugin - Panels map[string]PanelPlugin - StaticRoutes []*StaticRootConfig + DataSources map[string]*DataSourcePlugin + Panels map[string]*PanelPlugin + ApiPlugins map[string]*ApiPlugin + StaticRoutes []*PluginStaticRoute + Apps map[string]*AppPlugin + PluginTypes map[string]interface{} ) type PluginScanner struct { @@ -25,18 +32,45 @@ type PluginScanner struct { } func Init() error { - DataSources = make(map[string]DataSourcePlugin) - StaticRoutes = make([]*StaticRootConfig, 0) - Panels = make(map[string]PanelPlugin) + DataSources = make(map[string]*DataSourcePlugin) + ApiPlugins = make(map[string]*ApiPlugin) + StaticRoutes = make([]*PluginStaticRoute, 0) + Panels = make(map[string]*PanelPlugin) + Apps = make(map[string]*AppPlugin) + PluginTypes = map[string]interface{}{ + "panel": PanelPlugin{}, + "datasource": DataSourcePlugin{}, + "api": ApiPlugin{}, + "app": AppPlugin{}, + } scan(path.Join(setting.StaticRootPath, "app/plugins")) - scan(path.Join(setting.PluginsPath)) - checkExternalPluginPaths() - + checkPluginPaths() + // checkDependencies() return nil } -func checkExternalPluginPaths() error { +// func checkDependencies() { +// for appType, app := range Apps { +// for _, reqPanel := range app.PanelPlugins { +// if _, ok := Panels[reqPanel]; !ok { +// log.Fatal(4, "App %s requires Panel type %s, but it is not present.", appType, reqPanel) +// } +// } +// for _, reqDataSource := range app.DatasourcePlugins { +// if _, ok := DataSources[reqDataSource]; !ok { +// log.Fatal(4, "App %s requires DataSource type %s, but it is not present.", appType, reqDataSource) +// } +// } +// for _, reqApiPlugin := range app.ApiPlugins { +// if _, ok := ApiPlugins[reqApiPlugin]; !ok { +// log.Fatal(4, "App %s requires ApiPlugin type %s, but it is not present.", appType, reqApiPlugin) +// } +// } +// } +// } + +func checkPluginPaths() error { for _, section := range setting.Cfg.Sections() { if strings.HasPrefix(section.Name(), "plugin.") { path := section.Key("path").String() @@ -87,11 +121,26 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro return nil } -func addStaticRoot(staticRootConfig *StaticRootConfig, currentDir string) { - if staticRootConfig != nil { - staticRootConfig.Path = path.Join(currentDir, staticRootConfig.Path) - StaticRoutes = append(StaticRoutes, staticRootConfig) +func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) { + buf := new(bytes.Buffer) + buf.ReadFrom(reader) + jsonStr := buf.String() // + + tmpl, err := template.New("json").Parse(jsonStr) + if err != nil { + return nil, err } + + data := map[string]interface{}{ + "PluginPublicRoot": "public/plugins/" + pluginCommon.Id, + } + + var resultBuffer bytes.Buffer + if err := tmpl.ExecuteTemplate(&resultBuffer, "json", data); err != nil { + return nil, err + } + + return bytes.NewReader(resultBuffer.Bytes()), nil } func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { @@ -104,46 +153,29 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { defer reader.Close() jsonParser := json.NewDecoder(reader) - - pluginJson := make(map[string]interface{}) - if err := jsonParser.Decode(&pluginJson); err != nil { + pluginCommon := PluginBase{} + if err := jsonParser.Decode(&pluginCommon); err != nil { return err } - pluginType, exists := pluginJson["pluginType"] - if !exists { - return errors.New("Did not find pluginType property in plugin.json") + if pluginCommon.Id == "" || pluginCommon.Type == "" { + return errors.New("Did not find type and id property in plugin.json") } - if pluginType == "datasource" { - p := DataSourcePlugin{} - reader.Seek(0, 0) - if err := jsonParser.Decode(&p); err != nil { - return err - } - - if p.Type == "" { - return errors.New("Did not find type property in plugin.json") - } - - DataSources[p.Type] = p - addStaticRoot(p.StaticRootConfig, currentDir) + reader.Seek(0, 0) + if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil { + return err + } else { + jsonParser = json.NewDecoder(newReader) } - if pluginType == "panel" { - p := PanelPlugin{} - reader.Seek(0, 0) - if err := jsonParser.Decode(&p); err != nil { - return err - } + var loader PluginLoader - if p.Type == "" { - return errors.New("Did not find type property in plugin.json") - } - - Panels[p.Type] = p - addStaticRoot(p.StaticRootConfig, currentDir) + if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists { + return errors.New("Unkown plugin type " + pluginCommon.Type) + } else { + loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) } - return nil + return loader.Load(jsonParser, currentDir) } diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index bbeac4bba81..1808f72ec0d 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -18,5 +18,22 @@ func TestPluginScans(t *testing.T) { So(err, ShouldBeNil) So(len(DataSources), ShouldBeGreaterThan, 1) + So(len(Panels), ShouldBeGreaterThan, 1) + + Convey("Should set module automatically", func() { + So(DataSources["graphite"].Module, ShouldEqual, "app/plugins/datasource/graphite/module") + }) }) + + Convey("When reading app plugin definition", t, func() { + setting.Cfg = ini.Empty() + sec, _ := setting.Cfg.NewSection("plugin.app-test") + sec.NewKey("path", "../../tests/app-plugin-json") + err := Init() + + So(err, ShouldBeNil) + So(len(Apps), ShouldBeGreaterThan, 0) + So(Apps["app-example"].Info.Logos.Large, ShouldEqual, "public/plugins/app-example/img/logo_large.png") + }) + } diff --git a/pkg/plugins/queries.go b/pkg/plugins/queries.go new file mode 100644 index 00000000000..889cbe654d5 --- /dev/null +++ b/pkg/plugins/queries.go @@ -0,0 +1,75 @@ +package plugins + +import ( + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) { + query := m.GetAppSettingsQuery{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 + } + + return orgAppsMap, nil +} + +func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { + enabledPlugins := NewEnabledPlugins() + orgApps, err := GetOrgAppSettings(orgId) + if err != nil { + return nil, err + } + + seenPanels := make(map[string]bool) + seenApi := make(map[string]bool) + + for appId, installedApp := range Apps { + var app AppPlugin + app = *installedApp + + // 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 { + app.Enabled = b.Enabled + app.Pinned = b.Pinned + } + + if app.Enabled { + enabledPlugins.Apps = append(enabledPlugins.Apps, &app) + } + } + + // add all plugins that are not part of an App. + for d, installedDs := range DataSources { + if installedDs.App == "" { + enabledPlugins.DataSources[d] = installedDs + } + } + + for p, panel := range Panels { + if panel.App == "" { + if _, ok := seenPanels[p]; !ok { + seenPanels[p] = true + enabledPlugins.Panels = append(enabledPlugins.Panels, panel) + } + } + } + + for a, api := range ApiPlugins { + if api.App == "" { + if _, ok := seenApi[a]; !ok { + seenApi[a] = true + enabledPlugins.ApiList = append(enabledPlugins.ApiList, api) + } + } + } + + return &enabledPlugins, nil +} diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go new file mode 100644 index 00000000000..e9bfbeaa73b --- /dev/null +++ b/pkg/services/sqlstore/app_settings.go @@ -0,0 +1,50 @@ +package sqlstore + +import ( + "time" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func init() { + bus.AddHandler("sql", GetAppSettings) + 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 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, + Created: time.Now(), + Updated: time.Now(), + } + _, err = sess.Insert(&app) + return err + } else { + 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/plugin_bundle.go b/pkg/services/sqlstore/migrations/app_settings.go similarity index 56% rename from pkg/services/sqlstore/migrations/plugin_bundle.go rename to pkg/services/sqlstore/migrations/app_settings.go index b56ea74a13e..437debbe95b 100644 --- a/pkg/services/sqlstore/migrations/plugin_bundle.go +++ b/pkg/services/sqlstore/migrations/app_settings.go @@ -2,25 +2,27 @@ package migrations import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" -func addPluginBundleMigration(mg *Migrator) { +func addAppSettingsMigration(mg *Migrator) { - var pluginBundleV1 = Table{ - Name: "plugin_bundle", + appSettingsV1 := Table{ + Name: "app_settings", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: true}, - {Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "app_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}, {Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false}, }, Indices: []*Index{ - {Cols: []string{"org_id", "type"}, Type: UniqueIndex}, + {Cols: []string{"org_id", "app_id"}, Type: UniqueIndex}, }, } - mg.AddMigration("create plugin_bundle table v1", NewAddTableMigration(pluginBundleV1)) + + mg.AddMigration("create app_settings table v1", NewAddTableMigration(appSettingsV1)) //------- indexes ------------------ - addTableIndicesMigrations(mg, "v1", pluginBundleV1) + addTableIndicesMigrations(mg, "v3", appSettingsV1) } diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index c789db1966a..28ea3035bcd 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -18,7 +18,7 @@ func AddMigrations(mg *Migrator) { addApiKeyMigrations(mg) addDashboardSnapshotMigrations(mg) addQuotaMigration(mg) - addPluginBundleMigration(mg) + addAppSettingsMigration(mg) addSessionMigration(mg) addPlaylistMigrations(mg) } diff --git a/pkg/services/sqlstore/plugin_bundle.go b/pkg/services/sqlstore/plugin_bundle.go deleted file mode 100644 index c15c263a100..00000000000 --- a/pkg/services/sqlstore/plugin_bundle.go +++ /dev/null @@ -1,46 +0,0 @@ -package sqlstore - -import ( - "time" - - "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" -) - -func init() { - bus.AddHandler("sql", GetPluginBundles) - bus.AddHandler("sql", UpdatePluginBundle) -} - -func GetPluginBundles(query *m.GetPluginBundlesQuery) error { - sess := x.Where("org_id=?", query.OrgId) - - query.Result = make([]*m.PluginBundle, 0) - return sess.Find(&query.Result) -} - -func UpdatePluginBundle(cmd *m.UpdatePluginBundleCmd) error { - return inTransaction2(func(sess *session) error { - var bundle m.PluginBundle - - exists, err := sess.Where("org_id=? and type=?", cmd.OrgId, cmd.Type).Get(&bundle) - sess.UseBool("enabled") - if !exists { - bundle = m.PluginBundle{ - Type: cmd.Type, - OrgId: cmd.OrgId, - Enabled: cmd.Enabled, - JsonData: cmd.JsonData, - Created: time.Now(), - Updated: time.Now(), - } - _, err = sess.Insert(&bundle) - return err - } else { - bundle.Enabled = cmd.Enabled - bundle.JsonData = cmd.JsonData - _, err = sess.Id(bundle.Id).Update(&bundle) - return err - } - }) -} diff --git a/public/app/core/controllers/all.js b/public/app/core/controllers/all.js index 0d39cf57d69..d22010cffdc 100644 --- a/public/app/core/controllers/all.js +++ b/public/app/core/controllers/all.js @@ -1,9 +1,3 @@ -// import grafanaCtrl from './grafana_ctrl'; -// -// import * as asd from './sidemenu_ctrl'; -// -// export {grafanaCtrl}; - define([ './grafana_ctrl', './search_ctrl', diff --git a/public/app/core/controllers/sidemenu_ctrl.js b/public/app/core/controllers/sidemenu_ctrl.js index 671e89f7851..5bca63cb1cd 100644 --- a/public/app/core/controllers/sidemenu_ctrl.js +++ b/public/app/core/controllers/sidemenu_ctrl.js @@ -19,7 +19,8 @@ function (angular, _, $, coreModule, config) { $scope.mainLinks.push({ text: item.text, icon: item.icon, - href: $scope.getUrl(item.href) + img: item.img, + url: $scope.getUrl(item.url) }); }); }; diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js index 97361ed02d3..b3d6de2585d 100644 --- a/public/app/core/directives/misc.js +++ b/public/app/core/directives/misc.js @@ -62,12 +62,13 @@ function (angular, coreModule, kbn) { var label = ''; - var template = '' + ' '; - template = label + template; + template = template + label; elem.replaceWith($compile(angular.element(template))(scope)); } }; diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index 99cd4030bb8..cc4d73ef708 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -10,6 +10,7 @@ define([ $locationProvider.html5Mode(true); var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all'); + var loadAppsBundle = new BundleLoader.BundleLoader('app/features/apps/all'); $routeProvider .when('/', { @@ -41,17 +42,17 @@ define([ controller : 'DashboardImportCtrl', }) .when('/datasources', { - templateUrl: 'app/features/org/partials/datasources.html', + templateUrl: 'app/features/datasources/partials/list.html', controller : 'DataSourcesCtrl', resolve: loadOrgBundle, }) .when('/datasources/edit/:id', { - templateUrl: 'app/features/org/partials/datasourceEdit.html', + templateUrl: 'app/features/datasources/partials/edit.html', controller : 'DataSourceEditCtrl', resolve: loadOrgBundle, }) .when('/datasources/new', { - templateUrl: 'app/features/org/partials/datasourceEdit.html', + templateUrl: 'app/features/datasources/partials/edit.html', controller : 'DataSourceEditCtrl', resolve: loadOrgBundle, }) @@ -131,15 +132,17 @@ define([ templateUrl: 'app/partials/reset_password.html', controller : 'ResetPasswordCtrl', }) - .when('/plugins', { - templateUrl: 'app/features/org/partials/plugins.html', - controller: 'PluginsCtrl', - resolve: loadOrgBundle, + .when('/apps', { + templateUrl: 'app/features/apps/partials/list.html', + controller: 'AppListCtrl', + controllerAs: 'ctrl', + resolve: loadAppsBundle, }) - .when('/plugins/edit/:type', { - templateUrl: 'app/features/org/partials/pluginEdit.html', - controller: 'PluginEditCtrl', - resolve: loadOrgBundle, + .when('/apps/edit/:appId', { + templateUrl: 'app/features/apps/partials/edit.html', + controller: 'AppEditCtrl', + controllerAs: 'ctrl', + resolve: loadAppsBundle, }) .when('/global-alerts', { templateUrl: 'app/features/dashboard/partials/globalAlerts.html', diff --git a/public/app/core/services/alert_srv.js b/public/app/core/services/alert_srv.js index cecc8eadadf..463be459659 100644 --- a/public/app/core/services/alert_srv.js +++ b/public/app/core/services/alert_srv.js @@ -46,6 +46,10 @@ function (angular, _, coreModule) { }, timeout); } + if (!$rootScope.$$phase) { + $rootScope.$digest(); + } + return(newAlert); }; diff --git a/public/app/core/services/datasource_srv.js b/public/app/core/services/datasource_srv.js index 3811daad168..07e3f004d45 100644 --- a/public/app/core/services/datasource_srv.js +++ b/public/app/core/services/datasource_srv.js @@ -7,7 +7,7 @@ define([ function (angular, _, coreModule, config) { 'use strict'; - coreModule.default.service('datasourceSrv', function($q, $injector) { + coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope) { var self = this; this.init = function() { @@ -58,18 +58,27 @@ function (angular, _, coreModule, config) { } var deferred = $q.defer(); - var pluginDef = dsConfig.meta; - System.import(pluginDef.module).then(function() { - var AngularService = $injector.get(pluginDef.serviceName); - var instance = new AngularService(dsConfig, pluginDef); + System.import(pluginDef.module).then(function(plugin) { + // check if its in cache now + if (self.datasources[name]) { + deferred.resolve(self.datasources[name]); + return; + } + + // plugin module needs to export a constructor function named Datasource + if (!plugin.Datasource) { + throw "Plugin module is missing Datasource constructor"; + } + + var instance = $injector.instantiate(plugin.Datasource, {instanceSettings: dsConfig}); instance.meta = pluginDef; instance.name = name; self.datasources[name] = instance; deferred.resolve(instance); }).catch(function(err) { - console.log('Failed to load data source: ' + err); + $rootScope.appEvent('alert-error', [dsConfig.name + ' plugin failed', err.toString()]); }); return deferred.promise; diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 0f244998c17..875f205c1f9 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -44,7 +44,7 @@ diff --git a/public/app/features/apps/all.ts b/public/app/features/apps/all.ts new file mode 100644 index 00000000000..fcdd27dff4d --- /dev/null +++ b/public/app/features/apps/all.ts @@ -0,0 +1,2 @@ +import './edit_ctrl'; +import './list_ctrl'; diff --git a/public/app/features/apps/app_srv.ts b/public/app/features/apps/app_srv.ts new file mode 100644 index 00000000000..18c6979b388 --- /dev/null +++ b/public/app/features/apps/app_srv.ts @@ -0,0 +1,43 @@ +/// + +import _ from 'lodash'; +import angular from 'angular'; + +export class AppSrv { + apps: any = {}; + + /** @ngInject */ + constructor( + private $rootScope, + private $timeout, + private $q, + private backendSrv) { + } + + get(type) { + return this.getAll().then(() => { + return this.apps[type]; + }); + } + + getAll() { + if (!_.isEmpty(this.apps)) { + return this.$q.when(this.apps); + } + + return this.backendSrv.get('api/org/apps').then(results => { + return results.reduce((prev, current) => { + prev[current.type] = current; + return prev; + }, this.apps); + }); + } + + update(app) { + return this.backendSrv.post('api/org/apps', app).then(resp => { + + }); + } +} + +angular.module('grafana.services').service('appSrv', AppSrv); diff --git a/public/app/features/apps/edit_ctrl.ts b/public/app/features/apps/edit_ctrl.ts new file mode 100644 index 00000000000..fe77c0a6797 --- /dev/null +++ b/public/app/features/apps/edit_ctrl.ts @@ -0,0 +1,44 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import _ from 'lodash'; + +export class AppEditCtrl { + appModel: any; + + /** @ngInject */ + constructor(private backendSrv: any, private $routeParams: any) {} + + init() { + this.appModel = {}; + this.backendSrv.get(`/api/org/apps/${this.$routeParams.appId}/settings`).then(result => { + this.appModel = result; + }); + } + + update(options) { + var updateCmd = _.extend({ + appId: this.appModel.appId, + orgId: this.appModel.orgId, + enabled: this.appModel.enabled, + pinned: this.appModel.pinned, + jsonData: this.appModel.jsonData, + }, options); + + this.backendSrv.post(`/api/org/apps/${this.$routeParams.appId}/settings`, updateCmd).then(function() { + window.location.href = window.location.href; + }); + } + + toggleEnabled() { + this.update({enabled: this.appModel.enabled}); + } + + togglePinned() { + this.update({pinned: this.appModel.pinned}); + } +} + +angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl); + diff --git a/public/app/features/apps/list_ctrl.ts b/public/app/features/apps/list_ctrl.ts new file mode 100644 index 00000000000..3f195536ae4 --- /dev/null +++ b/public/app/features/apps/list_ctrl.ts @@ -0,0 +1,19 @@ +/// + +import config = require('app/core/config'); +import angular from 'angular'; + +export class AppListCtrl { + apps: any[]; + + /** @ngInject */ + constructor(private backendSrv: any) {} + + init() { + this.backendSrv.get('api/org/apps').then(apps => { + this.apps = apps; + }); + } +} + +angular.module('grafana.controllers').controller('AppListCtrl', AppListCtrl); diff --git a/public/app/features/apps/partials/edit.html b/public/app/features/apps/partials/edit.html new file mode 100644 index 00000000000..ff022299ac2 --- /dev/null +++ b/public/app/features/apps/partials/edit.html @@ -0,0 +1,102 @@ + + + + +
+ + +
+

+ {{ctrl.appModel.name}} +

+ + {{ctrl.appModel.info.description}}
+ + Version: {{ctrl.appModel.info.version}}     Updated: {{ctrl.appModel.info.updated}} + + +
+

+ +
+ +       + +
+ +
+

Included with app:

+
+
+
+ + Dashboards +
+
    +
  • None
  • +
+
+
+
+ + Panels +
+
    +
  • None
  • +
+
+
+
+ + Datasources +
+
    +
  • None
  • +
+
+
+
+ + Pages +
+ +
+ +
+
+ +
+

Dependencies:

+
+ Grafana 2.6.x +
+
+ +
+

Configuration:

+
+
+
+ + +
+
diff --git a/public/app/features/apps/partials/list.html b/public/app/features/apps/partials/list.html new file mode 100644 index 00000000000..c8d284712a5 --- /dev/null +++ b/public/app/features/apps/partials/list.html @@ -0,0 +1,51 @@ + + + + +
+
+

Apps

+ +
+ No apps defined +
+ +
    +
  • + +
  • +
+
+
diff --git a/public/app/features/dashboard/dashboardCtrl.js b/public/app/features/dashboard/dashboardCtrl.js index eb2aa3bad89..20b3122230c 100644 --- a/public/app/features/dashboard/dashboardCtrl.js +++ b/public/app/features/dashboard/dashboardCtrl.js @@ -106,14 +106,6 @@ function (angular, $, config, moment) { }; }; - $scope.panelEditorPath = function(type) { - return 'app/' + config.panels[type].path + '/editor.html'; - }; - - $scope.pulldownEditorPath = function(type) { - return 'app/panels/'+type+'/editor.html'; - }; - $scope.showJsonEditor = function(evt, options) { var editScope = $rootScope.$new(); editScope.object = options.object; diff --git a/public/app/features/datasources/all.js b/public/app/features/datasources/all.js new file mode 100644 index 00000000000..b181fd475c2 --- /dev/null +++ b/public/app/features/datasources/all.js @@ -0,0 +1,4 @@ +define([ + './list_ctrl', + './edit_ctrl', +], function () {}); diff --git a/public/app/features/org/datasourceEditCtrl.js b/public/app/features/datasources/edit_ctrl.js similarity index 78% rename from public/app/features/org/datasourceEditCtrl.js rename to public/app/features/datasources/edit_ctrl.js index b7f141f480e..468be95e24b 100644 --- a/public/app/features/org/datasourceEditCtrl.js +++ b/public/app/features/datasources/edit_ctrl.js @@ -9,26 +9,14 @@ function (angular, _, config) { var module = angular.module('grafana.controllers'); var datasourceTypes = []; + module.directive('datasourceHttpSettings', function() { + return {templateUrl: 'app/features/datasources/partials/http_settings.html'}; + }); + module.controller('DataSourceEditCtrl', function($scope, $q, backendSrv, $routeParams, $location, datasourceSrv) { - $scope.httpConfigPartialSrc = 'app/features/org/partials/datasourceHttpConfig.html'; - var defaults = {name: '', type: 'graphite', url: '', access: 'proxy', jsonData: {}}; - $scope.indexPatternTypes = [ - {name: 'No pattern', value: undefined}, - {name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'}, - {name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'}, - {name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'}, - {name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'}, - {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, - ]; - - $scope.esVersions = [ - {name: '1.x', value: 1}, - {name: '2.x', value: 2}, - ]; - $scope.init = function() { $scope.isNew = true; $scope.datasources = []; @@ -59,7 +47,7 @@ function (angular, _, config) { backendSrv.get('/api/datasources/' + id).then(function(ds) { $scope.isNew = false; $scope.current = ds; - $scope.typeChanged(); + return $scope.typeChanged(); }); }; @@ -127,12 +115,6 @@ function (angular, _, config) { } }; - $scope.indexPatternTypeChanged = function() { - var def = _.findWhere($scope.indexPatternTypes, {value: $scope.current.jsonData.interval}); - $scope.current.database = def.example || 'es-index-name'; - }; - $scope.init(); - }); }); diff --git a/public/app/features/org/datasourcesCtrl.js b/public/app/features/datasources/list_ctrl.js similarity index 100% rename from public/app/features/org/datasourcesCtrl.js rename to public/app/features/datasources/list_ctrl.js diff --git a/public/app/features/org/partials/datasourceEdit.html b/public/app/features/datasources/partials/edit.html similarity index 95% rename from public/app/features/org/partials/datasourceEdit.html rename to public/app/features/datasources/partials/edit.html index 6ea33e5a43c..fda9acde58a 100644 --- a/public/app/features/org/partials/datasourceEdit.html +++ b/public/app/features/datasources/partials/edit.html @@ -42,7 +42,7 @@
-
+
Testing....
diff --git a/public/app/features/org/partials/datasourceHttpConfig.html b/public/app/features/datasources/partials/http_settings.html similarity index 99% rename from public/app/features/org/partials/datasourceHttpConfig.html rename to public/app/features/datasources/partials/http_settings.html index 4b5a72dbf11..bd70e419326 100644 --- a/public/app/features/org/partials/datasourceHttpConfig.html +++ b/public/app/features/datasources/partials/http_settings.html @@ -53,3 +53,5 @@
+ +
diff --git a/public/app/features/org/partials/datasources.html b/public/app/features/datasources/partials/list.html similarity index 100% rename from public/app/features/org/partials/datasources.html rename to public/app/features/datasources/partials/list.html diff --git a/public/app/features/org/all.js b/public/app/features/org/all.js index be9668e33de..d232b3bcd0a 100644 --- a/public/app/features/org/all.js +++ b/public/app/features/org/all.js @@ -1,13 +1,8 @@ define([ - './datasourcesCtrl', - './datasourceEditCtrl', './orgUsersCtrl', './newOrgCtrl', './userInviteCtrl', './orgApiKeysCtrl', './orgDetailsCtrl', - './pluginsCtrl', - './pluginEditCtrl', - './plugin_srv', - './plugin_directive', + '../datasources/all', ], function () {}); diff --git a/public/app/features/org/partials/pluginConfigCore.html b/public/app/features/org/partials/pluginConfigCore.html deleted file mode 100644 index 1b13b46d0e5..00000000000 --- a/public/app/features/org/partials/pluginConfigCore.html +++ /dev/null @@ -1,3 +0,0 @@ -
-{{current.type}} plugin does not have any additional config. -
diff --git a/public/app/features/org/partials/pluginEdit.html b/public/app/features/org/partials/pluginEdit.html deleted file mode 100644 index 9276fce277c..00000000000 --- a/public/app/features/org/partials/pluginEdit.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - -
-
-

Edit Plugin

- - -
-
-
    -
  • - Type -
  • -
  • -
  • - -
  • - -
  • - Default  - - -
  • -
-
-
-
- -
- - Cancel -
-
- - -
-
\ No newline at end of file diff --git a/public/app/features/org/partials/plugins.html b/public/app/features/org/partials/plugins.html deleted file mode 100644 index 97949649094..00000000000 --- a/public/app/features/org/partials/plugins.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - -
-
-

Plugins

- -
- No plugins defined -
- -
-   +   {{annotation.name}}
- - - - - - - - - - -
Type
-   - {{p.type}} - - - - Edit - - - Enabled  - - -
- - - \ No newline at end of file diff --git a/public/app/features/org/pluginEditCtrl.js b/public/app/features/org/pluginEditCtrl.js deleted file mode 100644 index b4ba2c05653..00000000000 --- a/public/app/features/org/pluginEditCtrl.js +++ /dev/null @@ -1,35 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/config', -], -function (angular, _, config) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('PluginEditCtrl', function($scope, pluginSrv, $routeParams) { - $scope.init = function() { - $scope.current = {}; - $scope.getPlugins(); - }; - - $scope.getPlugins = function() { - pluginSrv.get($routeParams.type).then(function(result) { - $scope.current = _.clone(result); - }); - }; - - $scope.update = function() { - $scope._update(); - }; - - $scope._update = function() { - pluginSrv.update($scope.current).then(function() { - window.location.href = config.appSubUrl + "plugins"; - }); - }; - - $scope.init(); - }); -}); \ No newline at end of file diff --git a/public/app/features/org/plugin_directive.js b/public/app/features/org/plugin_directive.js deleted file mode 100644 index 6fd30730264..00000000000 --- a/public/app/features/org/plugin_directive.js +++ /dev/null @@ -1,47 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('pluginConfigLoader', function($compile) { - return { - restrict: 'E', - link: function(scope, elem) { - var directive = 'grafana-plugin-core'; - //wait for the parent scope to be applied. - scope.$watch("current", function(newVal) { - if (newVal) { - if (newVal.module) { - directive = 'grafana-plugin-'+newVal.type; - } - scope.require([newVal.module], function () { - var panelEl = angular.element(document.createElement(directive)); - elem.append(panelEl); - $compile(panelEl)(scope); - }); - } - }); - } - }; - }); - - module.directive('grafanaPluginCore', function() { - return { - restrict: 'E', - templateUrl: 'app/features/org/partials/pluginConfigCore.html', - transclude: true, - link: function(scope) { - scope.update = function() { - //Perform custom save events to the plugins own backend if needed. - - // call parent update to commit the change to the plugin object. - // this will cause the page to reload. - scope._update(); - }; - } - }; - }); -}); \ No newline at end of file diff --git a/public/app/features/org/plugin_srv.js b/public/app/features/org/plugin_srv.js deleted file mode 100644 index 863828c7659..00000000000 --- a/public/app/features/org/plugin_srv.js +++ /dev/null @@ -1,58 +0,0 @@ -define([ - 'angular', - 'lodash', -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('pluginSrv', function($rootScope, $timeout, $q, backendSrv) { - var self = this; - this.init = function() { - console.log("pluginSrv init"); - this.plugins = {}; - }; - - this.get = function(type) { - return $q(function(resolve) { - if (type in self.plugins) { - return resolve(self.plugins[type]); - } - backendSrv.get('/api/plugins').then(function(results) { - _.forEach(results, function(p) { - self.plugins[p.type] = p; - }); - return resolve(self.plugins[type]); - }); - }); - }; - - this.getAll = function() { - return $q(function(resolve) { - if (!_.isEmpty(self.plugins)) { - return resolve(self.plugins); - } - backendSrv.get('api/plugins').then(function(results) { - _.forEach(results, function(p) { - self.plugins[p.type] = p; - }); - return resolve(self.plugins); - }); - }); - }; - - this.update = function(plugin) { - return $q(function(resolve, reject) { - backendSrv.post('/api/plugins', plugin).then(function(resp) { - self.plugins[plugin.type] = plugin; - resolve(resp); - }, function(resp) { - reject(resp); - }); - }); - }; - - this.init(); - }); -}); diff --git a/public/app/features/org/pluginsCtrl.js b/public/app/features/org/pluginsCtrl.js deleted file mode 100644 index 49dc104a840..00000000000 --- a/public/app/features/org/pluginsCtrl.js +++ /dev/null @@ -1,33 +0,0 @@ -define([ - 'angular', - 'app/core/config', -], -function (angular, config) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('PluginsCtrl', function($scope, $location, pluginSrv) { - - $scope.init = function() { - $scope.plugins = {}; - $scope.getPlugins(); - }; - - $scope.getPlugins = function() { - pluginSrv.getAll().then(function(result) { - console.log(result); - $scope.plugins = result; - }); - }; - - $scope.update = function(plugin) { - pluginSrv.update(plugin).then(function() { - window.location.href = config.appSubUrl + $location.path(); - }); - }; - - $scope.init(); - - }); -}); \ No newline at end of file diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js index c75c67ce825..0c3ab11a9a7 100644 --- a/public/app/features/panel/panel_directive.js +++ b/public/app/features/panel/panel_directive.js @@ -43,6 +43,33 @@ function (angular, $, config) { }; }); + module.directive('datasourceCustomSettingsView', function($compile) { + return { + restrict: 'E', + scope: { + dsMeta: "=", + current: "=", + }, + link: function(scope, elem) { + scope.$watch("dsMeta.module", function() { + if (!scope.dsMeta) { + return; + } + + System.import(scope.dsMeta.module).then(function() { + elem.empty(); + var panelEl = angular.element(document.createElement('datasource-custom-settings-view-' + scope.dsMeta.id)); + elem.append(panelEl); + $compile(panelEl)(scope); + }).catch(function(err) { + console.log('Failed to load plugin:', err); + scope.appEvent('alert-error', ['Plugin Load Error', 'Failed to load plugin ' + scope.dsMeta.id + ', ' + err]); + }); + }); + } + }; + }); + module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) { var self = this; @@ -62,12 +89,26 @@ function (angular, $, config) { editorScope = options.scope.$new(); datasourceSrv.get(newVal).then(function(ds) { - self.addDirective(options, ds.meta.type, editorScope); + self.addDirective(options, ds.meta.id, editorScope); }); }); }; }); + module.directive('datasourceEditorView', function(dynamicDirectiveSrv) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + dynamicDirectiveSrv.define({ + datasourceProperty: attrs.datasource, + name: attrs.name, + scope: scope, + parentElem: elem, + }); + } + }; + }); + module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) { return { restrict: 'E', @@ -90,7 +131,7 @@ function (angular, $, config) { scope.target.refId = 'A'; } - var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.type)); + var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.id)); elem.append(panelEl); $compile(panelEl)(editorScope); }); @@ -99,20 +140,6 @@ function (angular, $, config) { }; }); - module.directive('datasourceEditorView', function(dynamicDirectiveSrv) { - return { - restrict: 'E', - link: function(scope, elem, attrs) { - dynamicDirectiveSrv.define({ - datasourceProperty: attrs.datasource, - name: attrs.name, - scope: scope, - parentElem: elem, - }); - } - }; - }); - module.directive('panelResizer', function($rootScope) { return { restrict: 'E', diff --git a/public/app/partials/sidemenu.html b/public/app/partials/sidemenu.html index 21ee3af6a1b..87eab7b5fb4 100644 --- a/public/app/partials/sidemenu.html +++ b/public/app/partials/sidemenu.html @@ -34,13 +34,6 @@ -
  • - - - Sign in - -
  • -
  • @@ -52,8 +45,11 @@
  • - - + + + + + {{item.text}}
  • diff --git a/public/app/plugins/datasource/cloudwatch/datasource.d.ts b/public/app/plugins/datasource/cloudwatch/datasource.d.ts new file mode 100644 index 00000000000..a50d7ca49cc --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/datasource.d.ts @@ -0,0 +1,3 @@ +declare var Datasource: any; +export default Datasource; + diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index f606b6e3dc8..a2a74ca1883 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -4,24 +4,19 @@ define([ 'moment', 'app/core/utils/datemath', './query_ctrl', - './directives', ], function (angular, _, moment, dateMath) { 'use strict'; - var module = angular.module('grafana.services'); + /** @ngInject */ + function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'cloudwatch'; + this.name = instanceSettings.name; + this.supportMetrics = true; + this.proxyUrl = instanceSettings.url; + this.defaultRegion = instanceSettings.jsonData.defaultRegion; - module.factory('CloudWatchDatasource', function($q, backendSrv, templateSrv) { - - function CloudWatchDatasource(datasource) { - this.type = 'cloudwatch'; - this.name = datasource.name; - this.supportMetrics = true; - this.proxyUrl = datasource.url; - this.defaultRegion = datasource.jsonData.defaultRegion; - } - - CloudWatchDatasource.prototype.query = function(options) { + this.query = function(options) { var start = convertToCloudWatchTime(options.range.from, false); var end = convertToCloudWatchTime(options.range.to, true); @@ -72,7 +67,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) { + this.performTimeSeriesQuery = function(query, start, end) { return this.awsRequest({ region: query.region, action: 'GetMetricStatistics', @@ -88,15 +83,15 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.getRegions = function() { + this.getRegions = function() { return this.awsRequest({action: '__GetRegions'}); }; - CloudWatchDatasource.prototype.getNamespaces = function() { + this.getNamespaces = function() { return this.awsRequest({action: '__GetNamespaces'}); }; - CloudWatchDatasource.prototype.getMetrics = function(namespace) { + this.getMetrics = function(namespace) { return this.awsRequest({ action: '__GetMetrics', parameters: { @@ -105,7 +100,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.getDimensionKeys = function(namespace) { + this.getDimensionKeys = function(namespace) { return this.awsRequest({ action: '__GetDimensions', parameters: { @@ -114,7 +109,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) { + this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) { var request = { region: templateSrv.replace(region), action: 'ListMetrics', @@ -141,7 +136,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.performEC2DescribeInstances = function(region, filters, instanceIds) { + this.performEC2DescribeInstances = function(region, filters, instanceIds) { return this.awsRequest({ region: region, action: 'DescribeInstances', @@ -149,7 +144,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.metricFindQuery = function(query) { + this.metricFindQuery = function(query) { var region; var namespace; var metricName; @@ -210,7 +205,7 @@ function (angular, _, moment, dateMath) { return $q.when([]); }; - CloudWatchDatasource.prototype.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) { + this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) { return this.awsRequest({ region: region, action: 'DescribeAlarmsForMetric', @@ -218,7 +213,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) { + this.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) { return this.awsRequest({ region: region, action: 'DescribeAlarmHistory', @@ -226,7 +221,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.annotationQuery = function(options) { + this.annotationQuery = function(options) { var annotation = options.annotation; var region = templateSrv.replace(annotation.region); var namespace = templateSrv.replace(annotation.namespace); @@ -278,7 +273,7 @@ function (angular, _, moment, dateMath) { return d.promise; }; - CloudWatchDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { /* use billing metrics for test */ var region = this.defaultRegion; var namespace = 'AWS/Billing'; @@ -290,7 +285,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.awsRequest = function(data) { + this.awsRequest = function(data) { var options = { method: 'POST', url: this.proxyUrl, @@ -302,7 +297,7 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.getDefaultRegion = function() { + this.getDefaultRegion = function() { return this.defaultRegion; }; @@ -361,7 +356,7 @@ function (angular, _, moment, dateMath) { }); } - return CloudWatchDatasource; - }); + } + return CloudWatchDatasource; }); diff --git a/public/app/plugins/datasource/cloudwatch/directives.js b/public/app/plugins/datasource/cloudwatch/module.js similarity index 74% rename from public/app/plugins/datasource/cloudwatch/directives.js rename to public/app/plugins/datasource/cloudwatch/module.js index a08d4fc8d9a..bb9d9102063 100644 --- a/public/app/plugins/datasource/cloudwatch/directives.js +++ b/public/app/plugins/datasource/cloudwatch/module.js @@ -1,8 +1,9 @@ define([ 'angular', + './datasource', './query_parameter_ctrl', ], -function (angular) { +function (angular, CloudWatchDatasource) { 'use strict'; var module = angular.module('grafana.directives'); @@ -28,4 +29,11 @@ function (angular) { }; }); + module.directive('datasourceCustomSettingsViewCloudwatch', function() { + return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/edit_view.html'}; + }); + + return { + Datasource: CloudWatchDatasource + }; }); diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/edit_view.html similarity index 100% rename from public/app/plugins/datasource/cloudwatch/partials/config.html rename to public/app/plugins/datasource/cloudwatch/partials/edit_view.html diff --git a/public/app/plugins/datasource/cloudwatch/plugin.json b/public/app/plugins/datasource/cloudwatch/plugin.json index f1cf0e5512c..49c5341bd21 100644 --- a/public/app/plugins/datasource/cloudwatch/plugin.json +++ b/public/app/plugins/datasource/cloudwatch/plugin.json @@ -1,16 +1,7 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "CloudWatch", - - "type": "cloudwatch", - "serviceName": "CloudWatchDatasource", - - "module": "app/plugins/datasource/cloudwatch/datasource", - - "partials": { - "config": "app/plugins/datasource/cloudwatch/partials/config.html", - "query": "app/plugins/datasource/cloudwatch/partials/query.editor.html" - }, + "id": "cloudwatch", "metrics": true, "annotations": true diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 55603232fdf..7c5c06839de 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -3,25 +3,25 @@ import "../datasource"; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import moment from 'moment'; import helpers from 'test/specs/helpers'; +import Datasource from "../datasource"; describe('CloudWatchDatasource', function() { var ctx = new helpers.ServiceTestContext(); + var instanceSettings = { + jsonData: {defaultRegion: 'us-east-1', access: 'proxy'}, + }; beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(angularMocks.module('grafana.controllers')); - beforeEach(ctx.providePhase(['templateSrv', 'backendSrv'])); - beforeEach(ctx.createService('CloudWatchDatasource')); - beforeEach(function() { - ctx.ds = new ctx.service({ - jsonData: { - defaultRegion: 'us-east-1', - access: 'proxy' - } - }); - }); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + })); describe('When performing CloudWatch query', function() { var requestParams; diff --git a/public/app/plugins/datasource/elasticsearch/datasource.d.ts b/public/app/plugins/datasource/elasticsearch/datasource.d.ts new file mode 100644 index 00000000000..a50d7ca49cc --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/datasource.d.ts @@ -0,0 +1,3 @@ +declare var Datasource: any; +export default Datasource; + diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 72eaa4fcfd6..da31c6b50bb 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -7,33 +7,27 @@ define([ './index_pattern', './elastic_response', './query_ctrl', - './directives' ], function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) { 'use strict'; - var module = angular.module('grafana.services'); + /** @ngInject */ + function ElasticDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) { + this.basicAuth = instanceSettings.basicAuth; + this.withCredentials = instanceSettings.withCredentials; + this.url = instanceSettings.url; + this.name = instanceSettings.name; + this.index = instanceSettings.index; + this.timeField = instanceSettings.jsonData.timeField; + this.esVersion = instanceSettings.jsonData.esVersion; + this.indexPattern = new IndexPattern(instanceSettings.index, instanceSettings.jsonData.interval); + this.interval = instanceSettings.jsonData.timeInterval; + this.queryBuilder = new ElasticQueryBuilder({ + timeField: this.timeField, + esVersion: this.esVersion, + }); - module.factory('ElasticDatasource', function($q, backendSrv, templateSrv, timeSrv) { - - function ElasticDatasource(datasource) { - this.type = 'elasticsearch'; - this.basicAuth = datasource.basicAuth; - this.withCredentials = datasource.withCredentials; - this.url = datasource.url; - this.name = datasource.name; - this.index = datasource.index; - this.timeField = datasource.jsonData.timeField; - this.esVersion = datasource.jsonData.esVersion; - this.indexPattern = new IndexPattern(datasource.index, datasource.jsonData.interval); - this.interval = datasource.jsonData.timeInterval; - this.queryBuilder = new ElasticQueryBuilder({ - timeField: this.timeField, - esVersion: this.esVersion, - }); - } - - ElasticDatasource.prototype._request = function(method, url, data) { + this._request = function(method, url, data) { var options = { url: this.url + "/" + url, method: method, @@ -52,21 +46,21 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes return backendSrv.datasourceRequest(options); }; - ElasticDatasource.prototype._get = function(url) { + this._get = function(url) { return this._request('GET', this.indexPattern.getIndexForToday() + url) - .then(function(results) { - return results.data; - }); + .then(function(results) { + return results.data; + }); }; - ElasticDatasource.prototype._post = function(url, data) { + this._post = function(url, data) { return this._request('POST', url, data) - .then(function(results) { - return results.data; - }); + .then(function(results) { + return results.data; + }); }; - ElasticDatasource.prototype.annotationQuery = function(options) { + this.annotationQuery = function(options) { var annotation = options.annotation; var timeField = annotation.timeField || '@timestamp'; var queryString = annotation.query || '*'; @@ -147,7 +141,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { return this._get('/_stats').then(function() { return { status: "success", message: "Data source is working", title: "Success" }; }, function(err) { @@ -159,13 +153,13 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.getQueryHeader = function(searchType, timeFrom, timeTo) { + this.getQueryHeader = function(searchType, timeFrom, timeTo) { var header = {search_type: searchType, "ignore_unavailable": true}; header.index = this.indexPattern.getIndexList(timeFrom, timeTo); return angular.toJson(header); }; - ElasticDatasource.prototype.query = function(options) { + this.query = function(options) { var payload = ""; var target; var sentTargets = []; @@ -203,7 +197,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.getFields = function(query) { + this.getFields = function(query) { return this._get('/_mapping').then(function(res) { var fields = {}; var typeMap = { @@ -240,7 +234,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.getTerms = function(queryDef) { + this.getTerms = function(queryDef) { var range = timeSrv.timeRange(); var header = this.getQueryHeader('count', range.from, range.to); var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef)); @@ -258,7 +252,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.metricFindQuery = function(query) { + this.metricFindQuery = function(query) { query = templateSrv.replace(query); query = angular.fromJson(query); if (!query) { @@ -273,14 +267,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes } }; - ElasticDatasource.prototype.getDashboard = function(id) { + this.getDashboard = function(id) { return this._get('/dashboard/' + id) .then(function(result) { return angular.fromJson(result._source.dashboard); }); }; - ElasticDatasource.prototype.searchDashboards = function() { + this.searchDashboards = function() { var query = { query: { query_string: { query: '*' } }, size: 10000, @@ -308,7 +302,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes return displayHits; }); }; + } - return ElasticDatasource; - }); + return ElasticDatasource; }); diff --git a/public/app/plugins/datasource/elasticsearch/directives.js b/public/app/plugins/datasource/elasticsearch/directives.js index a7ad8f6dfbf..3bb27885ef2 100644 --- a/public/app/plugins/datasource/elasticsearch/directives.js +++ b/public/app/plugins/datasource/elasticsearch/directives.js @@ -20,6 +20,10 @@ function (angular) { return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; }); + module.directive('elastic', function() { + return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/config.html'}; + }); + module.directive('elasticMetricAgg', function() { return { templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', diff --git a/public/app/plugins/datasource/elasticsearch/edit_view.ts b/public/app/plugins/datasource/elasticsearch/edit_view.ts new file mode 100644 index 00000000000..8315a389953 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/edit_view.ts @@ -0,0 +1,38 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +export class EditViewCtrl { + + constructor($scope) { + $scope.indexPatternTypes = [ + {name: 'No pattern', value: undefined}, + {name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'}, + {name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'}, + {name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'}, + {name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'}, + {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, + ]; + + $scope.esVersions = [ + {name: '1.x', value: 1}, + {name: '2.x', value: 2}, + ]; + + $scope.indexPatternTypeChanged = function() { + var def = _.findWhere($scope.indexPatternTypes, {value: $scope.current.jsonData.interval}); + $scope.current.database = def.example || 'es-index-name'; + }; + } +} + +function editViewDirective() { + return { + templateUrl: 'app/plugins/datasource/elasticsearch/partials/edit_view.html', + controller: EditViewCtrl, + }; +}; + + +export default editViewDirective; diff --git a/public/app/plugins/datasource/elasticsearch/module.js b/public/app/plugins/datasource/elasticsearch/module.js new file mode 100644 index 00000000000..958eb3eb8e7 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/module.js @@ -0,0 +1,60 @@ +define([ + 'angular', + './datasource', + './edit_view', + './bucket_agg', + './metric_agg', +], +function (angular, ElasticDatasource, editView) { + 'use strict'; + + var module = angular.module('grafana.directives'); + + module.directive('metricQueryEditorElasticsearch', function() { + return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'}; + }); + + module.directive('metricQueryOptionsElasticsearch', function() { + return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'}; + }); + + module.directive('annotationsQueryEditorElasticsearch', function() { + return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; + }); + + module.directive('elasticMetricAgg', function() { + return { + templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', + controller: 'ElasticMetricAggCtrl', + restrict: 'E', + scope: { + target: "=", + index: "=", + onChange: "&", + getFields: "&", + esVersion: '=' + } + }; + }); + + module.directive('elasticBucketAgg', function() { + return { + templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html', + controller: 'ElasticBucketAggCtrl', + restrict: 'E', + scope: { + target: "=", + index: "=", + onChange: "&", + getFields: "&", + } + }; + }); + + module.directive('datasourceCustomSettingsViewElasticsearch', editView.default); + + return { + Datasource: ElasticDatasource, + }; + +}); diff --git a/public/app/plugins/datasource/elasticsearch/partials/config.html b/public/app/plugins/datasource/elasticsearch/partials/edit_view.html similarity index 94% rename from public/app/plugins/datasource/elasticsearch/partials/config.html rename to public/app/plugins/datasource/elasticsearch/partials/edit_view.html index 595588c1be0..63a70ab8912 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/config.html +++ b/public/app/plugins/datasource/elasticsearch/partials/edit_view.html @@ -1,5 +1,4 @@ -
    -
    +
    Elasticsearch details
    @@ -42,8 +41,8 @@
    - +
    Default query settings
    @@ -53,7 +52,7 @@
  • + spellcheck='false' placeholder="example: >10s">
  • diff --git a/public/app/plugins/datasource/elasticsearch/plugin.json b/public/app/plugins/datasource/elasticsearch/plugin.json index a0350bd8c6c..7e975a7e93b 100644 --- a/public/app/plugins/datasource/elasticsearch/plugin.json +++ b/public/app/plugins/datasource/elasticsearch/plugin.json @@ -1,16 +1,7 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "Elasticsearch", - - "type": "elasticsearch", - "serviceName": "ElasticDatasource", - - "module": "app/plugins/datasource/elasticsearch/datasource", - - "partials": { - "config": "app/plugins/datasource/elasticsearch/partials/config.html", - "annotations": "app/plugins/datasource/elasticsearch/partials/annotations.editor.html" - }, + "id": "elasticsearch", "defaultMatchFormat": "lucene", "annotations": true, diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index f34d52b42df..a7e6a642550 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -1,28 +1,32 @@ -import "../datasource"; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import moment from 'moment'; import angular from 'angular'; import helpers from 'test/specs/helpers'; +import Datasource from "../datasource"; describe('ElasticDatasource', function() { var ctx = new helpers.ServiceTestContext(); + var instanceSettings: any = {jsonData: {}}; beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.providePhase(['templateSrv', 'backendSrv'])); - beforeEach(ctx.createService('ElasticDatasource')); - beforeEach(function() { - ctx.ds = new ctx.service({jsonData: {}}); - }); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.$injector = $injector; + })); + + function createDatasource(instanceSettings) { + instanceSettings.jsonData = instanceSettings.jsonData || {}; + ctx.ds = ctx.$injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + } describe('When testing datasource with index pattern', function() { beforeEach(function() { - ctx.ds = new ctx.service({ - url: 'http://es.com', - index: '[asd-]YYYY.MM.DD', - jsonData: { interval: 'Daily' } - }); + createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}}); }); it('should translate index pattern to current day', function() { @@ -44,11 +48,7 @@ describe('ElasticDatasource', function() { var requestOptions, parts, header; beforeEach(function() { - ctx.ds = new ctx.service({ - url: 'http://es.com', - index: '[asd-]YYYY.MM.DD', - jsonData: { interval: 'Daily' } - }); + createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}}); ctx.backendSrv.datasourceRequest = function(options) { requestOptions = options; @@ -83,7 +83,7 @@ describe('ElasticDatasource', function() { var requestOptions, parts, header; beforeEach(function() { - ctx.ds = new ctx.service({url: 'http://es.com', index: 'test', jsonData: {}}); + createDatasource({url: 'http://es.com', index: 'test'}); ctx.backendSrv.datasourceRequest = function(options) { requestOptions = options; diff --git a/public/app/plugins/datasource/grafana/datasource.js b/public/app/plugins/datasource/grafana/datasource.js deleted file mode 100644 index 2bc1e08137e..00000000000 --- a/public/app/plugins/datasource/grafana/datasource.js +++ /dev/null @@ -1,30 +0,0 @@ -define([ - 'angular' -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.factory('GrafanaDatasource', function($q, backendSrv) { - - function GrafanaDatasource() { - } - - GrafanaDatasource.prototype.query = function(options) { - return backendSrv.get('/api/metrics/test', { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), - maxDataPoints: options.maxDataPoints - }); - }; - - GrafanaDatasource.prototype.metricFindQuery = function() { - return $q.when([]); - }; - - return GrafanaDatasource; - - }); - -}); diff --git a/public/app/plugins/datasource/grafana/directives.js b/public/app/plugins/datasource/grafana/directives.js deleted file mode 100644 index 9c29340e430..00000000000 --- a/public/app/plugins/datasource/grafana/directives.js +++ /dev/null @@ -1,13 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorGrafana', function() { - return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'}; - }); - -}); diff --git a/public/app/plugins/datasource/grafana/plugin.json b/public/app/plugins/datasource/grafana/plugin.json index 8d4ba70e471..fdccb24b59d 100644 --- a/public/app/plugins/datasource/grafana/plugin.json +++ b/public/app/plugins/datasource/grafana/plugin.json @@ -1,11 +1,8 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "Grafana", + "id": "grafana", + "builtIn": true, - - "type": "grafana", - "serviceName": "GrafanaDatasource", - - "module": "app/plugins/datasource/grafana/datasource", "metrics": true } diff --git a/public/app/plugins/datasource/graphite/datasource.d.ts b/public/app/plugins/datasource/graphite/datasource.d.ts new file mode 100644 index 00000000000..a50d7ca49cc --- /dev/null +++ b/public/app/plugins/datasource/graphite/datasource.d.ts @@ -0,0 +1,3 @@ +declare var Datasource: any; +export default Datasource; + diff --git a/public/app/plugins/datasource/graphite/datasource.js b/public/app/plugins/datasource/graphite/datasource.js index 49aa589db45..3a169eaae4e 100644 --- a/public/app/plugins/datasource/graphite/datasource.js +++ b/public/app/plugins/datasource/graphite/datasource.js @@ -4,7 +4,6 @@ define([ 'jquery', 'app/core/config', 'app/core/utils/datemath', - './directives', './query_ctrl', './func_editor', './add_graphite_func', @@ -12,20 +11,16 @@ define([ function (angular, _, $, config, dateMath) { 'use strict'; - var module = angular.module('grafana.services'); + /** @ngInject */ + function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.basicAuth = instanceSettings.basicAuth; + this.url = instanceSettings.url; + this.name = instanceSettings.name; + this.cacheTimeout = instanceSettings.cacheTimeout; + this.withCredentials = instanceSettings.withCredentials; + this.render_method = instanceSettings.render_method || 'POST'; - module.factory('GraphiteDatasource', function($q, backendSrv, templateSrv) { - - function GraphiteDatasource(datasource) { - this.basicAuth = datasource.basicAuth; - this.url = datasource.url; - this.name = datasource.name; - this.cacheTimeout = datasource.cacheTimeout; - this.withCredentials = datasource.withCredentials; - this.render_method = datasource.render_method || 'POST'; - } - - GraphiteDatasource.prototype.query = function(options) { + this.query = function(options) { try { var graphOptions = { from: this.translateTime(options.rangeRaw.from, false), @@ -62,7 +57,7 @@ function (angular, _, $, config, dateMath) { } }; - GraphiteDatasource.prototype.convertDataPointsToMs = function(result) { + this.convertDataPointsToMs = function(result) { if (!result || !result.data) { return []; } for (var i = 0; i < result.data.length; i++) { var series = result.data[i]; @@ -73,7 +68,7 @@ function (angular, _, $, config, dateMath) { return result; }; - GraphiteDatasource.prototype.annotationQuery = function(options) { + this.annotationQuery = function(options) { // Graphite metric as annotation if (options.annotation.target) { var target = templateSrv.replace(options.annotation.target); @@ -85,50 +80,49 @@ function (angular, _, $, config, dateMath) { }; return this.query(graphiteQuery) - .then(function(result) { - var list = []; + .then(function(result) { + var list = []; - for (var i = 0; i < result.data.length; i++) { - var target = result.data[i]; + for (var i = 0; i < result.data.length; i++) { + var target = result.data[i]; - for (var y = 0; y < target.datapoints.length; y++) { - var datapoint = target.datapoints[y]; - if (!datapoint[0]) { continue; } + for (var y = 0; y < target.datapoints.length; y++) { + var datapoint = target.datapoints[y]; + if (!datapoint[0]) { continue; } - list.push({ - annotation: options.annotation, - time: datapoint[1], - title: target.target - }); - } + list.push({ + annotation: options.annotation, + time: datapoint[1], + title: target.target + }); } + } - return list; - }); + return list; + }); } // Graphite event as annotation else { var tags = templateSrv.replace(options.annotation.tags); - return this.events({range: options.rangeRaw, tags: tags}) - .then(function(results) { - var list = []; - for (var i = 0; i < results.data.length; i++) { - var e = results.data[i]; + return this.events({range: options.rangeRaw, tags: tags}).then(function(results) { + var list = []; + for (var i = 0; i < results.data.length; i++) { + var e = results.data[i]; - list.push({ - annotation: options.annotation, - time: e.when * 1000, - title: e.what, - tags: e.tags, - text: e.data - }); - } - return list; - }); + list.push({ + annotation: options.annotation, + time: e.when * 1000, + title: e.what, + tags: e.tags, + text: e.data + }); + } + return list; + }); } }; - GraphiteDatasource.prototype.events = function(options) { + this.events = function(options) { try { var tags = ''; if (options.tags) { @@ -146,7 +140,7 @@ function (angular, _, $, config, dateMath) { } }; - GraphiteDatasource.prototype.translateTime = function(date, roundUp) { + this.translateTime = function(date, roundUp) { if (_.isString(date)) { if (date === 'now') { return 'now'; @@ -178,7 +172,7 @@ function (angular, _, $, config, dateMath) { return date.unix(); }; - GraphiteDatasource.prototype.metricFindQuery = function(query) { + this.metricFindQuery = function(query) { var interpolated; try { interpolated = encodeURIComponent(templateSrv.replace(query)); @@ -198,24 +192,24 @@ function (angular, _, $, config, dateMath) { }); }; - GraphiteDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { return this.metricFindQuery('*').then(function () { return { status: "success", message: "Data source is working", title: "Success" }; }); }; - GraphiteDatasource.prototype.listDashboards = function(query) { + this.listDashboards = function(query) { return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) .then(function(results) { return results.data.dashboards; }); }; - GraphiteDatasource.prototype.loadDashboard = function(dashName) { + this.loadDashboard = function(dashName) { return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) }); }; - GraphiteDatasource.prototype.doGraphiteRequest = function(options) { + this.doGraphiteRequest = function(options) { if (this.basicAuth || this.withCredentials) { options.withCredentials = true; } @@ -230,9 +224,9 @@ function (angular, _, $, config, dateMath) { return backendSrv.datasourceRequest(options); }; - GraphiteDatasource.prototype._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - GraphiteDatasource.prototype.buildGraphiteParams = function(options, scopedVars) { + this.buildGraphiteParams = function(options, scopedVars) { var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; var clean_options = [], targets = {}; var target, targetValue, i; @@ -296,9 +290,7 @@ function (angular, _, $, config, dateMath) { return clean_options; }; + } - return GraphiteDatasource; - - }); - + return GraphiteDatasource; }); diff --git a/public/app/plugins/datasource/graphite/directives.js b/public/app/plugins/datasource/graphite/module.js similarity index 69% rename from public/app/plugins/datasource/graphite/directives.js rename to public/app/plugins/datasource/graphite/module.js index 91e52bb9546..72dd9eeaf0a 100644 --- a/public/app/plugins/datasource/graphite/directives.js +++ b/public/app/plugins/datasource/graphite/module.js @@ -1,7 +1,8 @@ define([ 'angular', + './datasource', ], -function (angular) { +function (angular, GraphiteDatasource) { 'use strict'; var module = angular.module('grafana.directives'); @@ -18,4 +19,11 @@ function (angular) { return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'}; }); + module.directive('datasourceCustomSettingsViewGraphite', function() { + return {templateUrl: 'app/plugins/datasource/graphite/partials/config.html'}; + }); + + return { + Datasource: GraphiteDatasource, + }; }); diff --git a/public/app/plugins/datasource/graphite/partials/config.html b/public/app/plugins/datasource/graphite/partials/config.html index 0b454f4dc74..9f5259cb2ea 100644 --- a/public/app/plugins/datasource/graphite/partials/config.html +++ b/public/app/plugins/datasource/graphite/partials/config.html @@ -1,3 +1,2 @@ -
    - + diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index b170cc708f1..d2836b2a107 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -1,15 +1,7 @@ { - "pluginType": "datasource", "name": "Graphite", - - "type": "graphite", - "serviceName": "GraphiteDatasource", - - "module": "app/plugins/datasource/graphite/datasource", - - "partials": { - "config": "app/plugins/datasource/graphite/partials/config.html" - }, + "type": "datasource", + "id": "graphite", "defaultMatchFormat": "glob", "metrics": true, diff --git a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts index 2342b2a17fe..810e1be3516 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts @@ -1,19 +1,24 @@ -import "../datasource"; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import helpers from 'test/specs/helpers'; +import Datasource from "../datasource"; describe('graphiteDatasource', function() { var ctx = new helpers.ServiceTestContext(); + var instanceSettings: any = {url: ['']}; beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.providePhase(['backendSrv'])); - beforeEach(ctx.createService('GraphiteDatasource')); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.$injector = $injector; + })); beforeEach(function() { - ctx.ds = new ctx.service({ url: [''] }); + ctx.ds = ctx.$injector.instantiate(Datasource, {instanceSettings: instanceSettings}); }); describe('When querying influxdb with one target using query editor target spec', function() { diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js index 9516abdeacd..62eca66dd9e 100644 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ b/public/app/plugins/datasource/influxdb/datasource.js @@ -4,7 +4,6 @@ define([ 'app/core/utils/datemath', './influx_series', './influx_query', - './directives', './query_ctrl', ], function (angular, _, dateMath, InfluxSeries, InfluxQuery) { @@ -12,27 +11,22 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { InfluxQuery = InfluxQuery.default; - var module = angular.module('grafana.services'); + function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'influxdb'; + this.urls = _.map(instanceSettings.url.split(','), function(url) { + return url.trim(); + }); - module.factory('InfluxDatasource', function($q, backendSrv, templateSrv) { + this.username = instanceSettings.username; + this.password = instanceSettings.password; + this.name = instanceSettings.name; + this.database = instanceSettings.database; + this.basicAuth = instanceSettings.basicAuth; - function InfluxDatasource(datasource) { - this.type = 'influxdb'; - this.urls = _.map(datasource.url.split(','), function(url) { - return url.trim(); - }); + this.supportAnnotations = true; + this.supportMetrics = true; - this.username = datasource.username; - this.password = datasource.password; - this.name = datasource.name; - this.database = datasource.database; - this.basicAuth = datasource.basicAuth; - - this.supportAnnotations = true; - this.supportMetrics = true; - } - - InfluxDatasource.prototype.query = function(options) { + this.query = function(options) { var timeFilter = getTimeFilter(options); var queryTargets = []; var i, y; @@ -93,7 +87,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { }); }; - InfluxDatasource.prototype.annotationQuery = function(options) { + this.annotationQuery = function(options) { var timeFilter = getTimeFilter({rangeRaw: options.rangeRaw}); var query = options.annotation.query.replace('$timeFilter', timeFilter); query = templateSrv.replace(query); @@ -106,7 +100,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { }); }; - InfluxDatasource.prototype.metricFindQuery = function (query) { + this.metricFindQuery = function (query) { var interpolated; try { interpolated = templateSrv.replace(query); @@ -133,17 +127,17 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { }); }; - InfluxDatasource.prototype._seriesQuery = function(query) { + this._seriesQuery = function(query) { return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}); }; - InfluxDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { return this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(function () { return { status: "success", message: "Data source is working", title: "Success" }; }); }; - InfluxDatasource.prototype._influxRequest = function(method, url, data) { + this._influxRequest = function(method, url, data) { var self = this; var currentUrl = self.urls.shift(); @@ -219,9 +213,8 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { } return (date.valueOf() / 1000).toFixed(0) + 's'; } + } - return InfluxDatasource; - - }); + return InfluxDatasource; }); diff --git a/public/app/plugins/datasource/influxdb/directives.js b/public/app/plugins/datasource/influxdb/module.js similarity index 69% rename from public/app/plugins/datasource/influxdb/directives.js rename to public/app/plugins/datasource/influxdb/module.js index a4c66137751..2a15ecad839 100644 --- a/public/app/plugins/datasource/influxdb/directives.js +++ b/public/app/plugins/datasource/influxdb/module.js @@ -1,7 +1,8 @@ define([ 'angular', + './datasource', ], -function (angular) { +function (angular, InfluxDatasource) { 'use strict'; var module = angular.module('grafana.directives'); @@ -18,4 +19,11 @@ function (angular) { return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'}; }); + module.directive('datasourceCustomSettingsViewInfluxdb', function() { + return {templateUrl: 'app/plugins/datasource/influxdb/partials/config.html'}; + }); + + return { + Datasource: InfluxDatasource + }; }); diff --git a/public/app/plugins/datasource/influxdb/partials/config.html b/public/app/plugins/datasource/influxdb/partials/config.html index 4d51a33e6b2..8a85ec2a16a 100644 --- a/public/app/plugins/datasource/influxdb/partials/config.html +++ b/public/app/plugins/datasource/influxdb/partials/config.html @@ -1,5 +1,4 @@ -
    -
    +
    InfluxDB Details
    diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index d586d679367..4007010fd21 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -1,15 +1,7 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "InfluxDB 0.9.x", - - "type": "influxdb", - "serviceName": "InfluxDatasource", - - "module": "app/plugins/datasource/influxdb/datasource", - - "partials": { - "config": "app/plugins/datasource/influxdb/partials/config.html" - }, + "id": "influxdb", "defaultMatchFormat": "regex values", "metrics": true, diff --git a/public/app/plugins/datasource/mixed/datasource.js b/public/app/plugins/datasource/mixed/datasource.js deleted file mode 100644 index a74e872276b..00000000000 --- a/public/app/plugins/datasource/mixed/datasource.js +++ /dev/null @@ -1,35 +0,0 @@ -define([ - 'angular', - 'lodash', -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.factory('MixedDatasource', function($q, backendSrv, datasourceSrv) { - - function MixedDatasource() { - } - - MixedDatasource.prototype.query = function(options) { - var sets = _.groupBy(options.targets, 'datasource'); - var promises = _.map(sets, function(targets) { - return datasourceSrv.get(targets[0].datasource).then(function(ds) { - var opt = angular.copy(options); - opt.targets = targets; - return ds.query(opt); - }); - }); - - return $q.all(promises).then(function(results) { - return { data: _.flatten(_.pluck(results, 'data')) }; - }); - - }; - - return MixedDatasource; - - }); - -}); diff --git a/public/app/plugins/datasource/mixed/datasource.ts b/public/app/plugins/datasource/mixed/datasource.ts new file mode 100644 index 00000000000..bd0628fe210 --- /dev/null +++ b/public/app/plugins/datasource/mixed/datasource.ts @@ -0,0 +1,32 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +class MixedDatasource { + + constructor(private $q, private datasourceSrv) { + } + + query(options) { + var sets = _.groupBy(options.targets, 'datasource'); + var promises = _.map(sets, targets => { + var dsName = targets[0].datasource; + if (dsName === '-- Mixed --') { + return this.$q([]); + } + + return this.datasourceSrv.get(dsName).then(function(ds) { + var opt = angular.copy(options); + opt.targets = targets; + return ds.query(opt); + }); + }); + + return this.$q.all(promises).then(function(results) { + return { data: _.flatten(_.pluck(results, 'data')) }; + }); + } +} + +export {MixedDatasource, MixedDatasource as Datasource} diff --git a/public/app/plugins/datasource/mixed/plugin.json b/public/app/plugins/datasource/mixed/plugin.json index 85be108d995..b8c08446cb3 100644 --- a/public/app/plugins/datasource/mixed/plugin.json +++ b/public/app/plugins/datasource/mixed/plugin.json @@ -1,12 +1,9 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "Mixed datasource", + "id": "mixed", + "builtIn": true, "mixed": true, - - "type": "mixed", - "serviceName": "MixedDatasource", - - "module": "app/plugins/datasource/mixed/datasource", "metrics": true } diff --git a/public/app/plugins/datasource/opentsdb/datasource.d.ts b/public/app/plugins/datasource/opentsdb/datasource.d.ts new file mode 100644 index 00000000000..a50d7ca49cc --- /dev/null +++ b/public/app/plugins/datasource/opentsdb/datasource.d.ts @@ -0,0 +1,3 @@ +declare var Datasource: any; +export default Datasource; + diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index b7d4bd2d37b..67b3f43a1ed 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -3,25 +3,19 @@ define([ 'lodash', 'app/core/utils/datemath', 'moment', - './directives', './queryCtrl', ], function (angular, _, dateMath) { 'use strict'; - var module = angular.module('grafana.services'); - - module.factory('OpenTSDBDatasource', function($q, backendSrv, templateSrv) { - - function OpenTSDBDatasource(datasource) { - this.type = 'opentsdb'; - this.url = datasource.url; - this.name = datasource.name; - this.supportMetrics = true; - } + function OpenTSDBDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'opentsdb'; + this.url = instanceSettings.url; + this.name = instanceSettings.name; + this.supportMetrics = true; // Called once per panel (graph) - OpenTSDBDatasource.prototype.query = function(options) { + this.query = function(options) { var start = convertToTSDBTime(options.rangeRaw.from, false); var end = convertToTSDBTime(options.rangeRaw.to, true); var qs = []; @@ -60,7 +54,7 @@ function (angular, _, dateMath) { }); }; - OpenTSDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) { + this.performTimeSeriesQuery = function(queries, start, end) { var reqBody = { start: start, queries: queries @@ -80,13 +74,13 @@ function (angular, _, dateMath) { return backendSrv.datasourceRequest(options); }; - OpenTSDBDatasource.prototype._performSuggestQuery = function(query, type) { + this._performSuggestQuery = function(query, type) { return this._get('/api/suggest', {type: type, q: query, max: 1000}).then(function(result) { return result.data; }); }; - OpenTSDBDatasource.prototype._performMetricKeyValueLookup = function(metric, key) { + this._performMetricKeyValueLookup = function(metric, key) { if(!metric || !key) { return $q.when([]); } @@ -105,7 +99,7 @@ function (angular, _, dateMath) { }); }; - OpenTSDBDatasource.prototype._performMetricKeyLookup = function(metric) { + this._performMetricKeyLookup = function(metric) { if(!metric) { return $q.when([]); } return this._get('/api/search/lookup', {m: metric, limit: 1000}).then(function(result) { @@ -122,7 +116,7 @@ function (angular, _, dateMath) { }); }; - OpenTSDBDatasource.prototype._get = function(relativeUrl, params) { + this._get = function(relativeUrl, params) { return backendSrv.datasourceRequest({ method: 'GET', url: this.url + relativeUrl, @@ -130,7 +124,7 @@ function (angular, _, dateMath) { }); }; - OpenTSDBDatasource.prototype.metricFindQuery = function(query) { + this.metricFindQuery = function(query) { if (!query) { return $q.when([]); } var interpolated; @@ -181,14 +175,14 @@ function (angular, _, dateMath) { return $q.when([]); }; - OpenTSDBDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { return this._performSuggestQuery('cpu', 'metrics').then(function () { return { status: "success", message: "Data source is working", title: "Success" }; }); }; var aggregatorsPromise = null; - OpenTSDBDatasource.prototype.getAggregators = function() { + this.getAggregators = function() { if (aggregatorsPromise) { return aggregatorsPromise; } aggregatorsPromise = this._get('/api/aggregators').then(function(result) { @@ -311,7 +305,7 @@ function (angular, _, dateMath) { return date.valueOf(); } - return OpenTSDBDatasource; - }); + } + return OpenTSDBDatasource; }); diff --git a/public/app/plugins/datasource/opentsdb/directives.js b/public/app/plugins/datasource/opentsdb/module.js similarity index 53% rename from public/app/plugins/datasource/opentsdb/directives.js rename to public/app/plugins/datasource/opentsdb/module.js index 3ff4c3c2ccc..82a5a5e25d6 100644 --- a/public/app/plugins/datasource/opentsdb/directives.js +++ b/public/app/plugins/datasource/opentsdb/module.js @@ -1,7 +1,8 @@ define([ 'angular', + './datasource', ], -function (angular) { +function (angular, OpenTsDatasource) { 'use strict'; var module = angular.module('grafana.directives'); @@ -13,4 +14,11 @@ function (angular) { }; }); + module.directive('datasourceCustomSettingsViewOpentsdb', function() { + return {templateUrl: 'app/plugins/datasource/opentsdb/partials/config.html'}; + }); + + return { + Datasource: OpenTsDatasource + }; }); diff --git a/public/app/plugins/datasource/opentsdb/partials/config.html b/public/app/plugins/datasource/opentsdb/partials/config.html index bb5bdda1e20..9f5259cb2ea 100644 --- a/public/app/plugins/datasource/opentsdb/partials/config.html +++ b/public/app/plugins/datasource/opentsdb/partials/config.html @@ -1,4 +1,2 @@ -
    - -
    + diff --git a/public/app/plugins/datasource/opentsdb/plugin.json b/public/app/plugins/datasource/opentsdb/plugin.json index 311dcf0da9a..82fe2c32062 100644 --- a/public/app/plugins/datasource/opentsdb/plugin.json +++ b/public/app/plugins/datasource/opentsdb/plugin.json @@ -1,15 +1,7 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "OpenTSDB", - - "type": "opentsdb", - "serviceName": "OpenTSDBDatasource", - - "module": "app/plugins/datasource/opentsdb/datasource", - - "partials": { - "config": "app/plugins/datasource/opentsdb/partials/config.html" - }, + "id": "opentsdb", "metrics": true, "defaultMatchFormat": "pipe" diff --git a/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts new file mode 100644 index 00000000000..b1ad1b93737 --- /dev/null +++ b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts @@ -0,0 +1,71 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import helpers from 'test/specs/helpers'; +import Datasource from "../datasource"; + +describe('opentsdb', function() { + var ctx = new helpers.ServiceTestContext(); + var instanceSettings = {url: '' }; + + beforeEach(angularMocks.module('grafana.core')); + beforeEach(angularMocks.module('grafana.services')); + beforeEach(ctx.providePhase(['backendSrv'])); + + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + })); + + describe('When performing metricFindQuery', function() { + var results; + var requestOptions; + + beforeEach(function() { + ctx.backendSrv.datasourceRequest = function(options) { + requestOptions = options; + return ctx.$q.when({data: [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]] }]}); + }; + }); + + it('metrics() should generate api suggest query', function() { + ctx.ds.metricFindQuery('metrics(pew)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('metrics'); + expect(requestOptions.params.q).to.be('pew'); + }); + + it('tag_names(cpu) should generate looku query', function() { + ctx.ds.metricFindQuery('tag_names(cpu)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/search/lookup'); + expect(requestOptions.params.m).to.be('cpu'); + }); + + it('tag_values(cpu, test) should generate looku query', function() { + ctx.ds.metricFindQuery('tag_values(cpu, hostname)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/search/lookup'); + expect(requestOptions.params.m).to.be('cpu{hostname=*}'); + }); + + it('suggest_tagk() should generate api suggest query', function() { + ctx.ds.metricFindQuery('suggest_tagk(foo)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('tagk'); + expect(requestOptions.params.q).to.be('foo'); + }); + + it('suggest_tagv() should generate api suggest query', function() { + ctx.ds.metricFindQuery('suggest_tagv(bar)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('tagv'); + expect(requestOptions.params.q).to.be('bar'); + }); + }); + +}); + diff --git a/public/app/plugins/datasource/prometheus/datasource.d.ts b/public/app/plugins/datasource/prometheus/datasource.d.ts new file mode 100644 index 00000000000..a50d7ca49cc --- /dev/null +++ b/public/app/plugins/datasource/prometheus/datasource.d.ts @@ -0,0 +1,3 @@ +declare var Datasource: any; +export default Datasource; + diff --git a/public/app/plugins/datasource/prometheus/datasource.js b/public/app/plugins/datasource/prometheus/datasource.js index 391affb0c8d..9560fe8aa5f 100644 --- a/public/app/plugins/datasource/prometheus/datasource.js +++ b/public/app/plugins/datasource/prometheus/datasource.js @@ -3,31 +3,25 @@ define([ 'lodash', 'moment', 'app/core/utils/datemath', - './directives', './query_ctrl', ], function (angular, _, moment, dateMath) { 'use strict'; - var module = angular.module('grafana.services'); - var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; - module.factory('PrometheusDatasource', function($q, backendSrv, templateSrv) { + function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'prometheus'; + this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; + this.name = instanceSettings.name; + this.supportMetrics = true; + this.url = instanceSettings.url; + this.directUrl = instanceSettings.directUrl; + this.basicAuth = instanceSettings.basicAuth; + this.withCredentials = instanceSettings.withCredentials; + this.lastErrors = {}; - function PrometheusDatasource(datasource) { - this.type = 'prometheus'; - this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; - this.name = datasource.name; - this.supportMetrics = true; - this.url = datasource.url; - this.directUrl = datasource.directUrl; - this.basicAuth = datasource.basicAuth; - this.withCredentials = datasource.withCredentials; - this.lastErrors = {}; - } - - PrometheusDatasource.prototype._request = function(method, url) { + this._request = function(method, url) { var options = { url: this.url + url, method: method @@ -46,7 +40,7 @@ function (angular, _, moment, dateMath) { }; // Called once per panel (graph) - PrometheusDatasource.prototype.query = function(options) { + this.query = function(options) { var start = getPrometheusTime(options.range.from, false); var end = getPrometheusTime(options.range.to, true); @@ -86,31 +80,31 @@ function (angular, _, moment, dateMath) { var self = this; return $q.all(allQueryPromise) - .then(function(allResponse) { - var result = []; + .then(function(allResponse) { + var result = []; - _.each(allResponse, function(response, index) { - if (response.status === 'error') { - self.lastErrors.query = response.error; - throw response.error; - } - delete self.lastErrors.query; + _.each(allResponse, function(response, index) { + if (response.status === 'error') { + self.lastErrors.query = response.error; + throw response.error; + } + delete self.lastErrors.query; - _.each(response.data.data.result, function(metricData) { - result.push(transformMetricData(metricData, options.targets[index])); - }); + _.each(response.data.data.result, function(metricData) { + result.push(transformMetricData(metricData, options.targets[index])); }); - - return { data: result }; }); + + return { data: result }; + }); }; - PrometheusDatasource.prototype.performTimeSeriesQuery = function(query, start, end) { + this.performTimeSeriesQuery = function(query, start, end) { var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step; return this._request('GET', url); }; - PrometheusDatasource.prototype.performSuggestQuery = function(query) { + this.performSuggestQuery = function(query) { var url = '/api/v1/label/__name__/values'; return this._request('GET', url).then(function(result) { @@ -120,7 +114,7 @@ function (angular, _, moment, dateMath) { }); }; - PrometheusDatasource.prototype.metricFindQuery = function(query) { + this.metricFindQuery = function(query) { if (!query) { return $q.when([]); } var interpolated; @@ -196,7 +190,7 @@ function (angular, _, moment, dateMath) { } }; - PrometheusDatasource.prototype.testDatasource = function() { + this.testDatasource = function() { return this.metricFindQuery('metrics(.*)').then(function() { return { status: 'success', message: 'Data source is working', title: 'Success' }; }); @@ -276,8 +270,7 @@ function (angular, _, moment, dateMath) { } return (date.valueOf() / 1000).toFixed(0); } + } - return PrometheusDatasource; - }); - + return PrometheusDatasource; }); diff --git a/public/app/plugins/datasource/prometheus/directives.js b/public/app/plugins/datasource/prometheus/module.js similarity index 52% rename from public/app/plugins/datasource/prometheus/directives.js rename to public/app/plugins/datasource/prometheus/module.js index 2ceed8bffdb..91b8d498645 100644 --- a/public/app/plugins/datasource/prometheus/directives.js +++ b/public/app/plugins/datasource/prometheus/module.js @@ -1,7 +1,8 @@ define([ 'angular', + './datasource', ], -function (angular) { +function (angular, PromDatasource) { 'use strict'; var module = angular.module('grafana.directives'); @@ -10,4 +11,11 @@ function (angular) { return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'}; }); + module.directive('datasourceCustomSettingsViewPrometheus', function() { + return {templateUrl: 'app/plugins/datasource/prometheus/partials/config.html'}; + }); + + return { + Datasource: PromDatasource + }; }); diff --git a/public/app/plugins/datasource/prometheus/partials/config.html b/public/app/plugins/datasource/prometheus/partials/config.html index bb5bdda1e20..9f5259cb2ea 100644 --- a/public/app/plugins/datasource/prometheus/partials/config.html +++ b/public/app/plugins/datasource/prometheus/partials/config.html @@ -1,4 +1,2 @@ -
    - -
    + diff --git a/public/app/plugins/datasource/prometheus/plugin.json b/public/app/plugins/datasource/prometheus/plugin.json index 5c97866101d..4cd55605816 100644 --- a/public/app/plugins/datasource/prometheus/plugin.json +++ b/public/app/plugins/datasource/prometheus/plugin.json @@ -1,15 +1,7 @@ { - "pluginType": "datasource", + "type": "datasource", "name": "Prometheus", - - "type": "prometheus", - "serviceName": "PrometheusDatasource", - - "module": "app/plugins/datasource/prometheus/datasource", - - "partials": { - "config": "app/plugins/datasource/prometheus/partials/config.html" - }, + "id": "prometheus", "metrics": true } diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index a03e39a9351..92620678eaf 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -1,17 +1,20 @@ -import '../datasource'; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import moment from 'moment'; import helpers from 'test/specs/helpers'; +import Datasource from '../datasource'; describe('PrometheusDatasource', function() { - var ctx = new helpers.ServiceTestContext(); + var instanceSettings = {url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' }; + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.createService('PrometheusDatasource')); - beforeEach(function() { - ctx.ds = new ctx.service({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' }); - }); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + })); describe('When querying prometheus with one target using query editor target spec', function() { var results; diff --git a/public/app/plugins/datasource/sql/datasource.js b/public/app/plugins/datasource/sql/datasource.js deleted file mode 100644 index ae6e62286ba..00000000000 --- a/public/app/plugins/datasource/sql/datasource.js +++ /dev/null @@ -1,18 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.factory('SqlDatasource', function() { - - function SqlDatasource() { - } - - return SqlDatasource; - - }); - -}); diff --git a/public/app/plugins/datasource/sql/partials/config.html b/public/app/plugins/datasource/sql/partials/config.html deleted file mode 100644 index e6b7749a2f7..00000000000 --- a/public/app/plugins/datasource/sql/partials/config.html +++ /dev/null @@ -1,53 +0,0 @@ -

    SQL Options

    - -
    -
      -
    • - DB Type -
    • -
    • - -
    • -
    • - Host -
    • -
    • - -
    • -
    • - SSL  - - -
    • -
    -
    -
    -
    -
      -
    • - Database -
    • -
    • - -
    • -
    -
    -
    -
    -
      -
    • - User -
    • -
    • - -
    • -
    • - Password -
    • -
    • - -
    • -
    -
    -
    - diff --git a/public/app/plugins/datasource/sql/partials/query.editor.html b/public/app/plugins/datasource/sql/partials/query.editor.html deleted file mode 100644 index 0d6d21d0ad2..00000000000 --- a/public/app/plugins/datasource/sql/partials/query.editor.html +++ /dev/null @@ -1,17 +0,0 @@ - -
    -
    -
    -
    Test graph
    - -

    - This is just a test data source that generates random walk series. If this is your only data source - open the left side menu and navigate to the data sources admin screen and add your data sources. You can change - data source using the button to the left of the Add query button. -

    -
    -
    - -
    -
    - diff --git a/public/app/plugins/datasource/sql/plugin.json_ b/public/app/plugins/datasource/sql/plugin.json_ deleted file mode 100644 index 8d3f6effae7..00000000000 --- a/public/app/plugins/datasource/sql/plugin.json_ +++ /dev/null @@ -1,16 +0,0 @@ -{ - "pluginType": "datasource", - "name": "Generic SQL (prototype)", - - "type": "generic_sql", - "serviceName": "SqlDatasource", - - "module": "app/plugins/datasource/sql/datasource", - - "partials": { - "config": "app/plugins/datasource/sql/partials/config.html", - "query": "app/plugins/datasource/sql/partials/query.editor.html" - }, - - "metrics": true -} diff --git a/public/app/plugins/panels/dashlist/editor.html b/public/app/plugins/panel/dashlist/editor.html similarity index 100% rename from public/app/plugins/panels/dashlist/editor.html rename to public/app/plugins/panel/dashlist/editor.html diff --git a/public/app/plugins/panels/dashlist/module.html b/public/app/plugins/panel/dashlist/module.html similarity index 100% rename from public/app/plugins/panels/dashlist/module.html rename to public/app/plugins/panel/dashlist/module.html diff --git a/public/app/plugins/panels/dashlist/module.js b/public/app/plugins/panel/dashlist/module.js similarity index 91% rename from public/app/plugins/panels/dashlist/module.js rename to public/app/plugins/panel/dashlist/module.js index fddc762ffe2..d9152566975 100644 --- a/public/app/plugins/panels/dashlist/module.js +++ b/public/app/plugins/panel/dashlist/module.js @@ -14,7 +14,7 @@ function (angular, app, _, config, PanelMeta) { module.directive('grafanaPanelDashlist', function() { return { controller: 'DashListPanelCtrl', - templateUrl: 'app/plugins/panels/dashlist/module.html', + templateUrl: 'app/plugins/panel/dashlist/module.html', }; }); @@ -26,7 +26,7 @@ function (angular, app, _, config, PanelMeta) { fullscreen: true, }); - $scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/dashlist/editor.html'); + $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/dashlist/editor.html'); var defaults = { mode: 'starred', diff --git a/public/app/plugins/panel/dashlist/plugin.json b/public/app/plugins/panel/dashlist/plugin.json new file mode 100644 index 00000000000..1f1266d8444 --- /dev/null +++ b/public/app/plugins/panel/dashlist/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Dashboard list", + "id": "dashlist" +} diff --git a/public/app/plugins/panels/graph/axisEditor.html b/public/app/plugins/panel/graph/axisEditor.html similarity index 100% rename from public/app/plugins/panels/graph/axisEditor.html rename to public/app/plugins/panel/graph/axisEditor.html diff --git a/public/app/plugins/panels/graph/graph.js b/public/app/plugins/panel/graph/graph.js similarity index 100% rename from public/app/plugins/panels/graph/graph.js rename to public/app/plugins/panel/graph/graph.js diff --git a/public/app/plugins/panels/graph/graph.tooltip.js b/public/app/plugins/panel/graph/graph.tooltip.js similarity index 100% rename from public/app/plugins/panels/graph/graph.tooltip.js rename to public/app/plugins/panel/graph/graph.tooltip.js diff --git a/public/app/plugins/panels/graph/legend.js b/public/app/plugins/panel/graph/legend.js similarity index 100% rename from public/app/plugins/panels/graph/legend.js rename to public/app/plugins/panel/graph/legend.js diff --git a/public/app/plugins/panels/graph/legend.popover.html b/public/app/plugins/panel/graph/legend.popover.html similarity index 100% rename from public/app/plugins/panels/graph/legend.popover.html rename to public/app/plugins/panel/graph/legend.popover.html diff --git a/public/app/plugins/panels/graph/module.html b/public/app/plugins/panel/graph/module.html similarity index 100% rename from public/app/plugins/panels/graph/module.html rename to public/app/plugins/panel/graph/module.html diff --git a/public/app/plugins/panels/graph/module.js b/public/app/plugins/panel/graph/module.js similarity index 98% rename from public/app/plugins/panels/graph/module.js rename to public/app/plugins/panel/graph/module.js index 42ef167f911..50a5ab59317 100644 --- a/public/app/plugins/panels/graph/module.js +++ b/public/app/plugins/panel/graph/module.js @@ -17,7 +17,7 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { module.directive('grafanaPanelGraph', function() { return { controller: 'GraphCtrl', - templateUrl: 'app/plugins/panels/graph/module.html', + templateUrl: 'app/plugins/panel/graph/module.html', }; }); @@ -30,8 +30,8 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { metricsEditor: true, }); - $scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panels/graph/axisEditor.html'); - $scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panels/graph/styleEditor.html'); + $scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panel/graph/axisEditor.html'); + $scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panel/graph/styleEditor.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); diff --git a/public/app/plugins/panel/graph/plugin.json b/public/app/plugins/panel/graph/plugin.json new file mode 100644 index 00000000000..f603e5e4b06 --- /dev/null +++ b/public/app/plugins/panel/graph/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Graph", + "id": "graph" +} diff --git a/public/app/plugins/panels/graph/seriesOverridesCtrl.js b/public/app/plugins/panel/graph/seriesOverridesCtrl.js similarity index 100% rename from public/app/plugins/panels/graph/seriesOverridesCtrl.js rename to public/app/plugins/panel/graph/seriesOverridesCtrl.js diff --git a/public/app/plugins/panels/graph/styleEditor.html b/public/app/plugins/panel/graph/styleEditor.html similarity index 100% rename from public/app/plugins/panels/graph/styleEditor.html rename to public/app/plugins/panel/graph/styleEditor.html diff --git a/public/app/plugins/panels/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html similarity index 100% rename from public/app/plugins/panels/singlestat/editor.html rename to public/app/plugins/panel/singlestat/editor.html diff --git a/public/app/plugins/panels/singlestat/module.html b/public/app/plugins/panel/singlestat/module.html similarity index 100% rename from public/app/plugins/panels/singlestat/module.html rename to public/app/plugins/panel/singlestat/module.html diff --git a/public/app/plugins/panels/singlestat/module.js b/public/app/plugins/panel/singlestat/module.js similarity index 97% rename from public/app/plugins/panels/singlestat/module.js rename to public/app/plugins/panel/singlestat/module.js index 47b18ba526f..5c99cf7d3b1 100644 --- a/public/app/plugins/panels/singlestat/module.js +++ b/public/app/plugins/panel/singlestat/module.js @@ -16,7 +16,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { module.directive('grafanaPanelSinglestat', function() { return { controller: 'SingleStatCtrl', - templateUrl: 'app/plugins/panels/singlestat/module.html', + templateUrl: 'app/plugins/panel/singlestat/module.html', }; }); @@ -31,7 +31,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { $scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; - $scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/singlestat/editor.html'); + $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); // Set and populate defaults diff --git a/public/app/plugins/panel/singlestat/plugin.json b/public/app/plugins/panel/singlestat/plugin.json new file mode 100644 index 00000000000..8372e4b2c2d --- /dev/null +++ b/public/app/plugins/panel/singlestat/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Singlestat", + "id": "singlestat" +} diff --git a/public/app/plugins/panels/singlestat/singleStatPanel.js b/public/app/plugins/panel/singlestat/singleStatPanel.js similarity index 100% rename from public/app/plugins/panels/singlestat/singleStatPanel.js rename to public/app/plugins/panel/singlestat/singleStatPanel.js diff --git a/public/app/plugins/panels/table/controller.ts b/public/app/plugins/panel/table/controller.ts similarity index 100% rename from public/app/plugins/panels/table/controller.ts rename to public/app/plugins/panel/table/controller.ts diff --git a/public/app/plugins/panels/table/editor.html b/public/app/plugins/panel/table/editor.html similarity index 100% rename from public/app/plugins/panels/table/editor.html rename to public/app/plugins/panel/table/editor.html diff --git a/public/app/plugins/panels/table/editor.ts b/public/app/plugins/panel/table/editor.ts similarity index 100% rename from public/app/plugins/panels/table/editor.ts rename to public/app/plugins/panel/table/editor.ts diff --git a/public/app/plugins/panels/table/module.html b/public/app/plugins/panel/table/module.html similarity index 100% rename from public/app/plugins/panels/table/module.html rename to public/app/plugins/panel/table/module.html diff --git a/public/app/plugins/panels/table/module.ts b/public/app/plugins/panel/table/module.ts similarity index 98% rename from public/app/plugins/panels/table/module.ts rename to public/app/plugins/panel/table/module.ts index 72122ecbd97..4344eb41229 100644 --- a/public/app/plugins/panels/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -14,7 +14,7 @@ export function tablePanel() { 'use strict'; return { restrict: 'E', - templateUrl: 'app/plugins/panels/table/module.html', + templateUrl: 'app/plugins/panel/table/module.html', controller: TablePanelCtrl, link: function(scope, elem) { var data; diff --git a/public/app/plugins/panels/table/options.html b/public/app/plugins/panel/table/options.html similarity index 100% rename from public/app/plugins/panels/table/options.html rename to public/app/plugins/panel/table/options.html diff --git a/public/app/plugins/panel/table/plugin.json b/public/app/plugins/panel/table/plugin.json new file mode 100644 index 00000000000..7f8c0bb23cf --- /dev/null +++ b/public/app/plugins/panel/table/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Table", + "id": "table" +} diff --git a/public/app/plugins/panels/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts similarity index 100% rename from public/app/plugins/panels/table/renderer.ts rename to public/app/plugins/panel/table/renderer.ts diff --git a/public/app/plugins/panels/table/specs/renderer_specs.ts b/public/app/plugins/panel/table/specs/renderer_specs.ts similarity index 100% rename from public/app/plugins/panels/table/specs/renderer_specs.ts rename to public/app/plugins/panel/table/specs/renderer_specs.ts diff --git a/public/app/plugins/panels/table/specs/transformers_specs.ts b/public/app/plugins/panel/table/specs/transformers_specs.ts similarity index 100% rename from public/app/plugins/panels/table/specs/transformers_specs.ts rename to public/app/plugins/panel/table/specs/transformers_specs.ts diff --git a/public/app/plugins/panels/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts similarity index 100% rename from public/app/plugins/panels/table/transformers.ts rename to public/app/plugins/panel/table/transformers.ts diff --git a/public/app/plugins/panels/text/editor.html b/public/app/plugins/panel/text/editor.html similarity index 100% rename from public/app/plugins/panels/text/editor.html rename to public/app/plugins/panel/text/editor.html diff --git a/public/app/plugins/panels/text/module.html b/public/app/plugins/panel/text/module.html similarity index 100% rename from public/app/plugins/panels/text/module.html rename to public/app/plugins/panel/text/module.html diff --git a/public/app/plugins/panels/text/module.js b/public/app/plugins/panel/text/module.js similarity index 96% rename from public/app/plugins/panels/text/module.js rename to public/app/plugins/panel/text/module.js index 145b3be9b0c..d3add3a15b8 100644 --- a/public/app/plugins/panels/text/module.js +++ b/public/app/plugins/panel/text/module.js @@ -16,7 +16,7 @@ function (angular, app, _, require, PanelMeta) { module.directive('grafanaPanelText', function() { return { controller: 'TextPanelCtrl', - templateUrl: 'app/plugins/panels/text/module.html', + templateUrl: 'app/plugins/panel/text/module.html', }; }); @@ -28,7 +28,7 @@ function (angular, app, _, require, PanelMeta) { fullscreen: true, }); - $scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panels/text/editor.html'); + $scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panel/text/editor.html'); // Set and populate defaults var _d = { @@ -98,7 +98,6 @@ function (angular, app, _, require, PanelMeta) { console.log('Text panel error: ', e); $scope.content = $sce.trustAsHtml(html); } - if(!$scope.$$phase) { $scope.$digest(); } diff --git a/public/app/plugins/panel/text/plugin.json b/public/app/plugins/panel/text/plugin.json new file mode 100644 index 00000000000..4cf046cec36 --- /dev/null +++ b/public/app/plugins/panel/text/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Text", + "id": "text" +} diff --git a/public/app/plugins/panels/dashlist/plugin.json b/public/app/plugins/panels/dashlist/plugin.json deleted file mode 100644 index af9b9d8bbc8..00000000000 --- a/public/app/plugins/panels/dashlist/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pluginType": "panel", - - "name": "Dashboard list", - "type": "dashlist", - - "module": "app/plugins/panels/dashlist/module" -} diff --git a/public/app/plugins/panels/graph/plugin.json b/public/app/plugins/panels/graph/plugin.json deleted file mode 100644 index 8b683c9d750..00000000000 --- a/public/app/plugins/panels/graph/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pluginType": "panel", - - "name": "Graph", - "type": "graph", - - "module": "app/plugins/panels/graph/module" -} diff --git a/public/app/plugins/panels/singlestat/plugin.json b/public/app/plugins/panels/singlestat/plugin.json deleted file mode 100644 index dfb38d615c7..00000000000 --- a/public/app/plugins/panels/singlestat/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pluginType": "panel", - - "name": "Singlestat", - "type": "singlestat", - - "module": "app/plugins/panels/singlestat/module" -} diff --git a/public/app/plugins/panels/table/plugin.json b/public/app/plugins/panels/table/plugin.json deleted file mode 100644 index cdcfb7081dc..00000000000 --- a/public/app/plugins/panels/table/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pluginType": "panel", - - "name": "Table", - "type": "table", - - "module": "app/plugins/panels/table/module" -} diff --git a/public/app/plugins/panels/text/plugin.json b/public/app/plugins/panels/text/plugin.json deleted file mode 100644 index 4a6c039104b..00000000000 --- a/public/app/plugins/panels/text/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pluginType": "panel", - - "name": "Text", - "type": "text", - - "module": "app/plugins/panels/text/module" -} diff --git a/public/less/apps.less b/public/less/apps.less new file mode 100644 index 00000000000..b53b0272290 --- /dev/null +++ b/public/less/apps.less @@ -0,0 +1,26 @@ +.apps-side-box { + float: left; +} + +.apps-side-box-logo { + padding: 15px; + background: @grafanaPanelBackground; + width: 120px; + text-align: center; + img { + max-width: 100px; + } + margin-bottom: 10px; +} + +.app-side-box-links { + list-style: none; + margin: 0; + + li { + background: @grafanaPanelBackground; + margin-top: 4px; + padding-left: 10px; + line-height: 25px; + } +} diff --git a/public/less/filter-list.less b/public/less/filter-list.less index 181aedf4d3c..2e9ea259d0f 100644 --- a/public/less/filter-list.less +++ b/public/less/filter-list.less @@ -52,6 +52,12 @@ font-weight: normal; } +.filter-list-card-image { + width: 50px; + padding: 5px 50px 5px 5px; +} + + .filter-list-card-status { color: #777; font-size: 12px; diff --git a/public/less/gfbox.less b/public/less/gfbox.less index 46967d143c5..55330d26513 100644 --- a/public/less/gfbox.less +++ b/public/less/gfbox.less @@ -84,6 +84,11 @@ max-width: 1000px; } +.page-wide-margined { + margin-left: 170px; + max-width: 1000px; +} + .admin-page { max-width: 800px; margin-left: 10px; diff --git a/public/less/grafana.less b/public/less/grafana.less index dcd6b4b734d..9cf3630db3e 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -21,9 +21,11 @@ @import "tabs.less"; @import "timepicker.less"; @import "alerting.less"; +@import "apps.less"; @import "filter-controls.less"; @import "filter-list.less"; @import "filter-table.less"; +@import "simple-box.less"; .row-control-inner { padding:0px; diff --git a/public/less/sidemenu.less b/public/less/sidemenu.less index ac3470c65ea..6e9691eaa11 100644 --- a/public/less/sidemenu.less +++ b/public/less/sidemenu.less @@ -92,6 +92,10 @@ top: 5px; font-size: 150%; } + img { + left: 7px; + position: relative; + } } .sidemenu-item { diff --git a/public/less/simple-box.less b/public/less/simple-box.less new file mode 100644 index 00000000000..65cd63fe0e3 --- /dev/null +++ b/public/less/simple-box.less @@ -0,0 +1,46 @@ + +@simpleBoxBorderWidth: 0.2rem; +@simpleBoxMargin: 1.5rem; +@simpleBoxBodyPadding: 0.5rem 0 0.5rem 1rem; + +.simple-box { + margin-top: @simpleBoxMargin; + background: @grafanaPanelBackground; +} + +.simple-box-header { + font-weight: normal; + line-height: 2.5rem; + color: @textColor; + margin: 0; + padding-left: 1rem; + border-bottom: @simpleBoxBorderWidth solid @bodyBackground; +} + +.simple-box-column { + flex-direction: row; + width: 25%; + border-right: @simpleBoxBorderWidth solid @bodyBackground; + ul { + margin: 0.7rem 0 1rem 1.2rem; + } +} + +.simple-box-column:last-child { + border: none; +} + +.simple-box-column-header { + font-size: @fontSizeLarge; + i { + padding-right: 0.3rem; + } +} + +.simple-box-body { + padding: @simpleBoxBodyPadding; +} + +.flex-container { + display: flex; +} diff --git a/public/less/type.less b/public/less/type.less index 0aeec228d6a..1e958c4b097 100644 --- a/public/less/type.less +++ b/public/less/type.less @@ -245,3 +245,8 @@ address { font-style: normal; line-height: @baseLineHeight; } + +a.external-link { + color: @blue; + text-decoration: underline; +} diff --git a/public/test/lib/common.ts b/public/test/lib/common.ts index 523a97c2c03..c7e8147c9c9 100644 --- a/public/test/lib/common.ts +++ b/public/test/lib/common.ts @@ -2,6 +2,7 @@ var _global = (window); var beforeEach = _global.beforeEach; +var before = _global.before; var describe = _global.describe; var it = _global.it; var sinon = _global.sinon; @@ -9,10 +10,12 @@ var expect = _global.expect; var angularMocks = { module: _global.module, + inject: _global.inject, }; export { beforeEach, + before, describe, it, sinon, diff --git a/public/test/specs/graph-ctrl-specs.js b/public/test/specs/graph-ctrl-specs.js index 81be9a40b46..607c13cf528 100644 --- a/public/test/specs/graph-ctrl-specs.js +++ b/public/test/specs/graph-ctrl-specs.js @@ -2,7 +2,7 @@ define([ './helpers', 'app/features/panel/panel_srv', 'app/features/panel/panel_helper', - 'app/plugins/panels/graph/module' + 'app/plugins/panel/graph/module' ], function(helpers) { 'use strict'; diff --git a/public/test/specs/graph-specs.js b/public/test/specs/graph-specs.js index 29fdeeb825e..db3a4acf61f 100644 --- a/public/test/specs/graph-specs.js +++ b/public/test/specs/graph-specs.js @@ -3,7 +3,7 @@ define([ 'angular', 'jquery', 'app/core/time_series', - 'app/plugins/panels/graph/graph' + 'app/plugins/panel/graph/graph' ], function(helpers, angular, $, TimeSeries) { 'use strict'; diff --git a/public/test/specs/graph-tooltip-specs.js b/public/test/specs/graph-tooltip-specs.js index 9dc84daefa3..de39d855a70 100644 --- a/public/test/specs/graph-tooltip-specs.js +++ b/public/test/specs/graph-tooltip-specs.js @@ -1,6 +1,6 @@ define([ 'jquery', - 'app/plugins/panels/graph/graph.tooltip' + 'app/plugins/panel/graph/graph.tooltip' ], function($, GraphTooltip) { 'use strict'; diff --git a/public/test/specs/opentsdbDatasource-specs.js b/public/test/specs/opentsdbDatasource-specs.js deleted file mode 100644 index 663c9bd83bd..00000000000 --- a/public/test/specs/opentsdbDatasource-specs.js +++ /dev/null @@ -1,71 +0,0 @@ -define([ - './helpers', - 'app/plugins/datasource/opentsdb/datasource' -], function(helpers) { - 'use strict'; - - describe('opentsdb', function() { - var ctx = new helpers.ServiceTestContext(); - - beforeEach(module('grafana.core')); - beforeEach(module('grafana.services')); - beforeEach(ctx.providePhase(['backendSrv'])); - - beforeEach(ctx.createService('OpenTSDBDatasource')); - beforeEach(function() { - ctx.ds = new ctx.service({ url: [''] }); - }); - - describe('When performing metricFindQuery', function() { - var results; - var requestOptions; - - beforeEach(function() { - ctx.backendSrv.datasourceRequest = function(options) { - requestOptions = options; - return ctx.$q.when({data: [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]] }]}); - }; - }); - - it('metrics() should generate api suggest query', function() { - ctx.ds.metricFindQuery('metrics(pew)').then(function(data) { results = data; }); - ctx.$rootScope.$apply(); - expect(requestOptions.url).to.be('/api/suggest'); - expect(requestOptions.params.type).to.be('metrics'); - expect(requestOptions.params.q).to.be('pew'); - }); - - it('tag_names(cpu) should generate looku query', function() { - ctx.ds.metricFindQuery('tag_names(cpu)').then(function(data) { results = data; }); - ctx.$rootScope.$apply(); - expect(requestOptions.url).to.be('/api/search/lookup'); - expect(requestOptions.params.m).to.be('cpu'); - }); - - it('tag_values(cpu, test) should generate looku query', function() { - ctx.ds.metricFindQuery('tag_values(cpu, hostname)').then(function(data) { results = data; }); - ctx.$rootScope.$apply(); - expect(requestOptions.url).to.be('/api/search/lookup'); - expect(requestOptions.params.m).to.be('cpu{hostname=*}'); - }); - - it('suggest_tagk() should generate api suggest query', function() { - ctx.ds.metricFindQuery('suggest_tagk(foo)').then(function(data) { results = data; }); - ctx.$rootScope.$apply(); - expect(requestOptions.url).to.be('/api/suggest'); - expect(requestOptions.params.type).to.be('tagk'); - expect(requestOptions.params.q).to.be('foo'); - }); - - it('suggest_tagv() should generate api suggest query', function() { - ctx.ds.metricFindQuery('suggest_tagv(bar)').then(function(data) { results = data; }); - ctx.$rootScope.$apply(); - expect(requestOptions.url).to.be('/api/suggest'); - expect(requestOptions.params.type).to.be('tagv'); - expect(requestOptions.params.q).to.be('bar'); - }); - - }); - }); -}); - diff --git a/public/test/specs/seriesOverridesCtrl-specs.js b/public/test/specs/seriesOverridesCtrl-specs.js index 1290e5f0987..ba820f065ab 100644 --- a/public/test/specs/seriesOverridesCtrl-specs.js +++ b/public/test/specs/seriesOverridesCtrl-specs.js @@ -1,6 +1,6 @@ define([ './helpers', - 'app/plugins/panels/graph/seriesOverridesCtrl' + 'app/plugins/panel/graph/seriesOverridesCtrl' ], function(helpers) { 'use strict'; diff --git a/public/test/specs/singlestat-specs.js b/public/test/specs/singlestat-specs.js index 14e1ca63cca..733f7f37e98 100644 --- a/public/test/specs/singlestat-specs.js +++ b/public/test/specs/singlestat-specs.js @@ -2,7 +2,7 @@ define([ './helpers', 'app/features/panel/panel_srv', 'app/features/panel/panel_helper', - 'app/plugins/panels/singlestat/module' + 'app/plugins/panel/singlestat/module' ], function(helpers) { 'use strict'; diff --git a/public/views/index.html b/public/views/index.html index defd7cfe47f..c70be25ef9e 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -52,7 +52,7 @@ window.grafanaBootData = { user:[[.User]], settings: [[.Settings]], - pluginModules: [[.PluginJs]], + pluginModules: [[.PluginModules]], mainNavLinks: [[.MainNavLinks]] }; diff --git a/tasks/options/jscs.js b/tasks/options/jscs.js index c27c1aff09d..8296e59a506 100644 --- a/tasks/options/jscs.js +++ b/tasks/options/jscs.js @@ -3,7 +3,7 @@ module.exports = function(config) { src: [ 'Gruntfile.js', '<%= srcDir %>/app/**/*.js', - '<%= srcDir %>/plugins/**/*.js', + '<%= srcDir %>/plugin/**/*.js', '!<%= srcDir %>/app/dashboards/*' ], options: { diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index fd6603e16f4..ecd28d8c914 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -62,11 +62,11 @@ module.exports = function(config,grunt) { ]; var fs = require('fs'); - var panelPath = config.srcDir + '/app/plugins/panels'; + var panelPath = config.srcDir + '/app/plugins/panel'; - // create a module for each directory in public/app/panels/ + // create a module for each directory in public/app/panel/ fs.readdirSync(panelPath).forEach(function (panelName) { - requireModules[0].include.push('app/plugins/panels/'+panelName+'/module'); + requireModules[0].include.push('app/plugins/panel/'+panelName+'/module'); }); return { options: options }; diff --git a/tasks/options/watch.js b/tasks/options/watch.js index 2be128a3112..515877b102e 100644 --- a/tasks/options/watch.js +++ b/tasks/options/watch.js @@ -6,7 +6,7 @@ module.exports = function(config, grunt) { grunt.log.writeln('File Changed: ' + filepath); - if (/(\.html)$/.test(filepath)) { + if (/(\.html)|(\.json)$/.test(filepath)) { newPath = filepath.replace(/^public/, 'public_gen'); grunt.log.writeln('Copying to ' + newPath); grunt.file.copy(filepath, newPath); @@ -33,6 +33,9 @@ module.exports = function(config, grunt) { grunt.config(option, result); grunt.task.run('typescript:build'); grunt.task.run('tslint'); + // copy ts file also used by source maps + newPath = filepath.replace(/^public/, 'public_gen'); + grunt.file.copy(filepath, newPath); } }); diff --git a/tasks/systemjs_task.js b/tasks/systemjs_task.js index 6340ce532b8..0f1a63883e0 100644 --- a/tasks/systemjs_task.js +++ b/tasks/systemjs_task.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { var modules = [ 'app/app', 'app/features/all', - 'app/plugins/panels/**/module', + 'app/plugins/panel/**/module', 'app/plugins/datasource/graphite/datasource', 'app/plugins/datasource/influxdb/datasource', 'app/plugins/datasource/elasticsearch/datasource', diff --git a/tests/app-plugin-json/plugin.json b/tests/app-plugin-json/plugin.json new file mode 100644 index 00000000000..986f28c7e99 --- /dev/null +++ b/tests/app-plugin-json/plugin.json @@ -0,0 +1,44 @@ +{ + "type": "app", + "name": "App Example", + "id": "app-example", + + "staticRoot":" ./public", + "module": "app", + + "pages": [ + {"name": "Example1", "url": "/app-example", "reqRole": "Editor"} + ], + + "css": { + "light": "css/plugin.dark.css", + "dark": "css/plugin.light.css" + }, + + "info": { + "description": "Example Grafana App", + "author": { + "name": "Raintank Inc.", + "url": "http://raintank.io" + }, + "keywords": ["example"], + "logos": { + "small": "img/logo_small.png", + "large": "img/logo_large.png" + }, + "links": [ + {"name": "Project site", "url": "http://project.com"}, + {"name": "License & Terms", "url": "http://license.com"} + ], + "version": "1.0.0", + "updated": "2015-02-10" + }, + + "dependencies": { + "grafanaVersion": "2.6.x", + "plugins": [ + {"type": "datasource", "id": "graphite", "name": "Graphite", "version": "1.0.0"}, + {"type": "panel", "id": "graph", "name": "Graph", "version": "1.0.0"} + ] + } +}