mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'apps'
This commit is contained in:
@@ -41,8 +41,8 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
|
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
|
||||||
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
|
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
|
||||||
|
|
||||||
r.Get("/plugins", reqSignedIn, Index)
|
r.Get("/apps", reqSignedIn, Index)
|
||||||
r.Get("/plugins/edit/*", reqSignedIn, Index)
|
r.Get("/apps/edit/*", reqSignedIn, Index)
|
||||||
|
|
||||||
r.Get("/dashboard/*", reqSignedIn, Index)
|
r.Get("/dashboard/*", reqSignedIn, Index)
|
||||||
r.Get("/dashboard-solo/*", reqSignedIn, Index)
|
r.Get("/dashboard-solo/*", reqSignedIn, Index)
|
||||||
@@ -120,6 +120,11 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Get("/invites", wrap(GetPendingOrgInvites))
|
r.Get("/invites", wrap(GetPendingOrgInvites))
|
||||||
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
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)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
// create new org
|
// create new org
|
||||||
@@ -205,5 +210,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
// rendering
|
// rendering
|
||||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||||
|
|
||||||
|
InitApiPluginRoutes(r)
|
||||||
|
|
||||||
r.NotFound(NotFoundHandler)
|
r.NotFound(NotFoundHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
75
pkg/api/api_plugin.go
Normal file
75
pkg/api/api_plugin.go
Normal file
@@ -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}
|
||||||
|
}
|
||||||
59
pkg/api/app_settings.go
Normal file
59
pkg/api/app_settings.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
//"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
@@ -115,13 +116,19 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDataSourcePlugins(c *middleware.Context) {
|
func GetDataSourcePlugins(c *middleware.Context) {
|
||||||
dsList := make(map[string]interface{})
|
dsList := make(map[string]*plugins.DataSourcePlugin)
|
||||||
|
|
||||||
for key, value := range plugins.DataSources {
|
if enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId); err != nil {
|
||||||
if !value.BuiltIn {
|
c.JsonApiErr(500, "Failed to get org apps", err)
|
||||||
dsList[key] = value
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for key, value := range enabledPlugins.DataSources {
|
||||||
|
if !value.BuiltIn {
|
||||||
|
dsList[key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, dsList)
|
c.JSON(200, dsList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
pkg/api/dtos/apps.go
Normal file
33
pkg/api/dtos/apps.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@ type IndexViewData struct {
|
|||||||
GoogleAnalyticsId string
|
GoogleAnalyticsId string
|
||||||
GoogleTagManagerId string
|
GoogleTagManagerId string
|
||||||
|
|
||||||
PluginCss []*PluginCss
|
PluginCss []*PluginCss
|
||||||
PluginJs []string
|
PluginModules []string
|
||||||
MainNavLinks []*NavLink
|
MainNavLinks []*NavLink
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginCss struct {
|
type PluginCss struct {
|
||||||
@@ -21,5 +21,6 @@ type PluginCss struct {
|
|||||||
type NavLink struct {
|
type NavLink struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Icon string `json:"icon"`
|
Icon string `json:"icon"`
|
||||||
Href string `json:"href"`
|
Img string `json:"img"`
|
||||||
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
datasources := make(map[string]interface{})
|
datasources := make(map[string]interface{})
|
||||||
var defaultDatasource string
|
var defaultDatasource string
|
||||||
|
|
||||||
|
enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, ds := range orgDataSources {
|
for _, ds := range orgDataSources {
|
||||||
url := ds.Url
|
url := ds.Url
|
||||||
|
|
||||||
@@ -42,7 +47,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
"url": url,
|
"url": url,
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, exists := plugins.DataSources[ds.Type]
|
meta, exists := enabledPlugins.DataSources[ds.Type]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Error(3, "Could not find plugin definition for data source: %v", ds.Type)
|
log.Error(3, "Could not find plugin definition for data source: %v", ds.Type)
|
||||||
continue
|
continue
|
||||||
@@ -110,8 +115,8 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
panels := map[string]interface{}{}
|
panels := map[string]interface{}{}
|
||||||
for _, panel := range plugins.Panels {
|
for _, panel := range enabledPlugins.Panels {
|
||||||
panels[panel.Type] = map[string]interface{}{
|
panels[panel.Id] = map[string]interface{}{
|
||||||
"module": panel.Module,
|
"module": panel.Module,
|
||||||
"name": panel.Name,
|
"name": panel.Name,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"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{
|
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
||||||
Text: "Dashboards",
|
Text: "Dashboards",
|
||||||
Icon: "fa fa-fw fa-th-large",
|
Icon: "fa fa-fw fa-th-large",
|
||||||
Href: "/",
|
Url: "/",
|
||||||
})
|
})
|
||||||
|
|
||||||
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
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{
|
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
||||||
Text: "Data Sources",
|
Text: "Data Sources",
|
||||||
Icon: "fa fa-fw fa-database",
|
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
|
return &data, nil
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ func newMacaron() *macaron.Macaron {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range plugins.StaticRoutes {
|
for _, route := range plugins.StaticRoutes {
|
||||||
pluginRoute := path.Join("/public/plugins/", route.Url)
|
pluginRoute := path.Join("/public/plugins/", route.PluginId)
|
||||||
log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Path)
|
log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Directory)
|
||||||
mapStatic(m, route.Path, "", pluginRoute)
|
mapStatic(m, route.Directory, "", pluginRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapStatic(m, setting.StaticRootPath, "", "public")
|
mapStatic(m, setting.StaticRootPath, "", "public")
|
||||||
|
|||||||
@@ -253,3 +253,7 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
|
|||||||
|
|
||||||
ctx.JSON(status, resp)
|
ctx.JSON(status, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) HasUserRole(role m.RoleType) bool {
|
||||||
|
return ctx.OrgRole.Includes(role)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type PluginBundle struct {
|
type AppSettings struct {
|
||||||
Id int64
|
Id int64
|
||||||
Type string
|
AppId string
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
Pinned bool
|
||||||
JsonData map[string]interface{}
|
JsonData map[string]interface{}
|
||||||
|
|
||||||
Created time.Time
|
Created time.Time
|
||||||
@@ -17,18 +18,18 @@ type PluginBundle struct {
|
|||||||
// COMMANDS
|
// COMMANDS
|
||||||
|
|
||||||
// Also acts as api DTO
|
// Also acts as api DTO
|
||||||
type UpdatePluginBundleCmd struct {
|
type UpdateAppSettingsCmd struct {
|
||||||
Type string `json:"type" binding:"Required"`
|
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
Pinned bool `json:"pinned"`
|
||||||
JsonData map[string]interface{} `json:"jsonData"`
|
JsonData map[string]interface{} `json:"jsonData"`
|
||||||
|
|
||||||
Id int64 `json:"-"`
|
AppId string `json:"-"`
|
||||||
OrgId int64 `json:"-"`
|
OrgId int64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
// QUERIES
|
// QUERIES
|
||||||
type GetPluginBundlesQuery struct {
|
type GetAppSettingsQuery struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Result []*PluginBundle
|
Result []*AppSettings
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,17 @@ func (r RoleType) IsValid() bool {
|
|||||||
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR
|
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR || 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 {
|
type OrgUser struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
|
|||||||
43
pkg/plugins/app_plugin.go
Normal file
43
pkg/plugins/app_plugin.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
25
pkg/plugins/datasource_plugin.go
Normal file
25
pkg/plugins/datasource_plugin.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
48
pkg/plugins/frontend_plugin.go
Normal file
48
pkg/plugins/frontend_plugin.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,26 +1,74 @@
|
|||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
type DataSourcePlugin struct {
|
import (
|
||||||
Type string `json:"type"`
|
"encoding/json"
|
||||||
Name string `json:"name"`
|
|
||||||
ServiceName string `json:"serviceName"`
|
"github.com/grafana/grafana/pkg/models"
|
||||||
Module string `json:"module"`
|
)
|
||||||
Partials map[string]interface{} `json:"partials"`
|
|
||||||
DefaultMatchFormat string `json:"defaultMatchFormat"`
|
type PluginLoader interface {
|
||||||
Annotations bool `json:"annotations"`
|
Load(decoder *json.Decoder, pluginDir string) error
|
||||||
Metrics bool `json:"metrics"`
|
|
||||||
BuiltIn bool `json:"builtIn"`
|
|
||||||
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelPlugin struct {
|
type PluginBase struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Module string `json:"module"`
|
Id string `json:"id"`
|
||||||
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
|
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"`
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
pkg/plugins/panel_plugin.go
Normal file
19
pkg/plugins/panel_plugin.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@@ -14,9 +18,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DataSources map[string]DataSourcePlugin
|
DataSources map[string]*DataSourcePlugin
|
||||||
Panels map[string]PanelPlugin
|
Panels map[string]*PanelPlugin
|
||||||
StaticRoutes []*StaticRootConfig
|
ApiPlugins map[string]*ApiPlugin
|
||||||
|
StaticRoutes []*PluginStaticRoute
|
||||||
|
Apps map[string]*AppPlugin
|
||||||
|
PluginTypes map[string]interface{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginScanner struct {
|
type PluginScanner struct {
|
||||||
@@ -25,18 +32,45 @@ type PluginScanner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
DataSources = make(map[string]DataSourcePlugin)
|
DataSources = make(map[string]*DataSourcePlugin)
|
||||||
StaticRoutes = make([]*StaticRootConfig, 0)
|
ApiPlugins = make(map[string]*ApiPlugin)
|
||||||
Panels = make(map[string]PanelPlugin)
|
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.StaticRootPath, "app/plugins"))
|
||||||
scan(path.Join(setting.PluginsPath))
|
checkPluginPaths()
|
||||||
checkExternalPluginPaths()
|
// checkDependencies()
|
||||||
|
|
||||||
return nil
|
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() {
|
for _, section := range setting.Cfg.Sections() {
|
||||||
if strings.HasPrefix(section.Name(), "plugin.") {
|
if strings.HasPrefix(section.Name(), "plugin.") {
|
||||||
path := section.Key("path").String()
|
path := section.Key("path").String()
|
||||||
@@ -87,11 +121,26 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addStaticRoot(staticRootConfig *StaticRootConfig, currentDir string) {
|
func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) {
|
||||||
if staticRootConfig != nil {
|
buf := new(bytes.Buffer)
|
||||||
staticRootConfig.Path = path.Join(currentDir, staticRootConfig.Path)
|
buf.ReadFrom(reader)
|
||||||
StaticRoutes = append(StaticRoutes, staticRootConfig)
|
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 {
|
func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
|
||||||
@@ -104,46 +153,29 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
|
|||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
jsonParser := json.NewDecoder(reader)
|
jsonParser := json.NewDecoder(reader)
|
||||||
|
pluginCommon := PluginBase{}
|
||||||
pluginJson := make(map[string]interface{})
|
if err := jsonParser.Decode(&pluginCommon); err != nil {
|
||||||
if err := jsonParser.Decode(&pluginJson); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginType, exists := pluginJson["pluginType"]
|
if pluginCommon.Id == "" || pluginCommon.Type == "" {
|
||||||
if !exists {
|
return errors.New("Did not find type and id property in plugin.json")
|
||||||
return errors.New("Did not find pluginType property in plugin.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pluginType == "datasource" {
|
reader.Seek(0, 0)
|
||||||
p := DataSourcePlugin{}
|
if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil {
|
||||||
reader.Seek(0, 0)
|
return err
|
||||||
if err := jsonParser.Decode(&p); err != nil {
|
} else {
|
||||||
return err
|
jsonParser = json.NewDecoder(newReader)
|
||||||
}
|
|
||||||
|
|
||||||
if p.Type == "" {
|
|
||||||
return errors.New("Did not find type property in plugin.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
DataSources[p.Type] = p
|
|
||||||
addStaticRoot(p.StaticRootConfig, currentDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pluginType == "panel" {
|
var loader PluginLoader
|
||||||
p := PanelPlugin{}
|
|
||||||
reader.Seek(0, 0)
|
|
||||||
if err := jsonParser.Decode(&p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Type == "" {
|
if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists {
|
||||||
return errors.New("Did not find type property in plugin.json")
|
return errors.New("Unkown plugin type " + pluginCommon.Type)
|
||||||
}
|
} else {
|
||||||
|
loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
|
||||||
Panels[p.Type] = p
|
|
||||||
addStaticRoot(p.StaticRootConfig, currentDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return loader.Load(jsonParser, currentDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,22 @@ func TestPluginScans(t *testing.T) {
|
|||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(DataSources), ShouldBeGreaterThan, 1)
|
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")
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
75
pkg/plugins/queries.go
Normal file
75
pkg/plugins/queries.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
50
pkg/services/sqlstore/app_settings.go
Normal file
50
pkg/services/sqlstore/app_settings.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,25 +2,27 @@ package migrations
|
|||||||
|
|
||||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||||
|
|
||||||
func addPluginBundleMigration(mg *Migrator) {
|
func addAppSettingsMigration(mg *Migrator) {
|
||||||
|
|
||||||
var pluginBundleV1 = Table{
|
appSettingsV1 := Table{
|
||||||
Name: "plugin_bundle",
|
Name: "app_settings",
|
||||||
Columns: []*Column{
|
Columns: []*Column{
|
||||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||||
{Name: "org_id", Type: DB_BigInt, Nullable: 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: "enabled", Type: DB_Bool, Nullable: false},
|
||||||
|
{Name: "pinned", Type: DB_Bool, Nullable: false},
|
||||||
{Name: "json_data", Type: DB_Text, Nullable: true},
|
{Name: "json_data", Type: DB_Text, Nullable: true},
|
||||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||||
},
|
},
|
||||||
Indices: []*Index{
|
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 ------------------
|
//------- indexes ------------------
|
||||||
addTableIndicesMigrations(mg, "v1", pluginBundleV1)
|
addTableIndicesMigrations(mg, "v3", appSettingsV1)
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ func AddMigrations(mg *Migrator) {
|
|||||||
addApiKeyMigrations(mg)
|
addApiKeyMigrations(mg)
|
||||||
addDashboardSnapshotMigrations(mg)
|
addDashboardSnapshotMigrations(mg)
|
||||||
addQuotaMigration(mg)
|
addQuotaMigration(mg)
|
||||||
addPluginBundleMigration(mg)
|
addAppSettingsMigration(mg)
|
||||||
addSessionMigration(mg)
|
addSessionMigration(mg)
|
||||||
addPlaylistMigrations(mg)
|
addPlaylistMigrations(mg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
// import grafanaCtrl from './grafana_ctrl';
|
|
||||||
//
|
|
||||||
// import * as asd from './sidemenu_ctrl';
|
|
||||||
//
|
|
||||||
// export {grafanaCtrl};
|
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'./grafana_ctrl',
|
'./grafana_ctrl',
|
||||||
'./search_ctrl',
|
'./search_ctrl',
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ function (angular, _, $, coreModule, config) {
|
|||||||
$scope.mainLinks.push({
|
$scope.mainLinks.push({
|
||||||
text: item.text,
|
text: item.text,
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
href: $scope.getUrl(item.href)
|
img: item.img,
|
||||||
|
url: $scope.getUrl(item.url)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,12 +62,13 @@ function (angular, coreModule, kbn) {
|
|||||||
var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' +
|
var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' +
|
||||||
text + tip + '</label>';
|
text + tip + '</label>';
|
||||||
|
|
||||||
var template = '<input class="cr1" id="' + scope.$id + model + '" type="checkbox" ' +
|
var template =
|
||||||
|
'<input class="cr1" id="' + scope.$id + model + '" type="checkbox" ' +
|
||||||
' ng-model="' + model + '"' + ngchange +
|
' ng-model="' + model + '"' + ngchange +
|
||||||
' ng-checked="' + model + '"></input>' +
|
' ng-checked="' + model + '"></input>' +
|
||||||
' <label for="' + scope.$id + model + '" class="cr1"></label>';
|
' <label for="' + scope.$id + model + '" class="cr1"></label>';
|
||||||
|
|
||||||
template = label + template;
|
template = template + label;
|
||||||
elem.replaceWith($compile(angular.element(template))(scope));
|
elem.replaceWith($compile(angular.element(template))(scope));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ define([
|
|||||||
$locationProvider.html5Mode(true);
|
$locationProvider.html5Mode(true);
|
||||||
|
|
||||||
var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all');
|
var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all');
|
||||||
|
var loadAppsBundle = new BundleLoader.BundleLoader('app/features/apps/all');
|
||||||
|
|
||||||
$routeProvider
|
$routeProvider
|
||||||
.when('/', {
|
.when('/', {
|
||||||
@@ -41,17 +42,17 @@ define([
|
|||||||
controller : 'DashboardImportCtrl',
|
controller : 'DashboardImportCtrl',
|
||||||
})
|
})
|
||||||
.when('/datasources', {
|
.when('/datasources', {
|
||||||
templateUrl: 'app/features/org/partials/datasources.html',
|
templateUrl: 'app/features/datasources/partials/list.html',
|
||||||
controller : 'DataSourcesCtrl',
|
controller : 'DataSourcesCtrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/datasources/edit/:id', {
|
.when('/datasources/edit/:id', {
|
||||||
templateUrl: 'app/features/org/partials/datasourceEdit.html',
|
templateUrl: 'app/features/datasources/partials/edit.html',
|
||||||
controller : 'DataSourceEditCtrl',
|
controller : 'DataSourceEditCtrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/datasources/new', {
|
.when('/datasources/new', {
|
||||||
templateUrl: 'app/features/org/partials/datasourceEdit.html',
|
templateUrl: 'app/features/datasources/partials/edit.html',
|
||||||
controller : 'DataSourceEditCtrl',
|
controller : 'DataSourceEditCtrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
@@ -131,15 +132,17 @@ define([
|
|||||||
templateUrl: 'app/partials/reset_password.html',
|
templateUrl: 'app/partials/reset_password.html',
|
||||||
controller : 'ResetPasswordCtrl',
|
controller : 'ResetPasswordCtrl',
|
||||||
})
|
})
|
||||||
.when('/plugins', {
|
.when('/apps', {
|
||||||
templateUrl: 'app/features/org/partials/plugins.html',
|
templateUrl: 'app/features/apps/partials/list.html',
|
||||||
controller: 'PluginsCtrl',
|
controller: 'AppListCtrl',
|
||||||
resolve: loadOrgBundle,
|
controllerAs: 'ctrl',
|
||||||
|
resolve: loadAppsBundle,
|
||||||
})
|
})
|
||||||
.when('/plugins/edit/:type', {
|
.when('/apps/edit/:appId', {
|
||||||
templateUrl: 'app/features/org/partials/pluginEdit.html',
|
templateUrl: 'app/features/apps/partials/edit.html',
|
||||||
controller: 'PluginEditCtrl',
|
controller: 'AppEditCtrl',
|
||||||
resolve: loadOrgBundle,
|
controllerAs: 'ctrl',
|
||||||
|
resolve: loadAppsBundle,
|
||||||
})
|
})
|
||||||
.when('/global-alerts', {
|
.when('/global-alerts', {
|
||||||
templateUrl: 'app/features/dashboard/partials/globalAlerts.html',
|
templateUrl: 'app/features/dashboard/partials/globalAlerts.html',
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ function (angular, _, coreModule) {
|
|||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$rootScope.$$phase) {
|
||||||
|
$rootScope.$digest();
|
||||||
|
}
|
||||||
|
|
||||||
return(newAlert);
|
return(newAlert);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ define([
|
|||||||
function (angular, _, coreModule, config) {
|
function (angular, _, coreModule, config) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
coreModule.default.service('datasourceSrv', function($q, $injector) {
|
coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.init = function() {
|
this.init = function() {
|
||||||
@@ -58,18 +58,27 @@ function (angular, _, coreModule, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
var pluginDef = dsConfig.meta;
|
var pluginDef = dsConfig.meta;
|
||||||
|
|
||||||
System.import(pluginDef.module).then(function() {
|
System.import(pluginDef.module).then(function(plugin) {
|
||||||
var AngularService = $injector.get(pluginDef.serviceName);
|
// check if its in cache now
|
||||||
var instance = new AngularService(dsConfig, pluginDef);
|
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.meta = pluginDef;
|
||||||
instance.name = name;
|
instance.name = name;
|
||||||
self.datasources[name] = instance;
|
self.datasources[name] = instance;
|
||||||
deferred.resolve(instance);
|
deferred.resolve(instance);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.log('Failed to load data source: ' + err);
|
$rootScope.appEvent('alert-error', [dsConfig.name + ' plugin failed', err.toString()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<table class="grafana-options-table">
|
<table class="grafana-options-table">
|
||||||
<tr ng-repeat="annotation in annotations">
|
<tr ng-repeat="annotation in annotations">
|
||||||
<td style="width:90%">
|
<td style="width:90%">
|
||||||
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
|
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
|
||||||
{{annotation.name}}
|
{{annotation.name}}
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
|
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
|
||||||
|
|||||||
2
public/app/features/apps/all.ts
Normal file
2
public/app/features/apps/all.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import './edit_ctrl';
|
||||||
|
import './list_ctrl';
|
||||||
43
public/app/features/apps/app_srv.ts
Normal file
43
public/app/features/apps/app_srv.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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);
|
||||||
44
public/app/features/apps/edit_ctrl.ts
Normal file
44
public/app/features/apps/edit_ctrl.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
19
public/app/features/apps/list_ctrl.ts
Normal file
19
public/app/features/apps/list_ctrl.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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);
|
||||||
102
public/app/features/apps/partials/edit.html
Normal file
102
public/app/features/apps/partials/edit.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<topnav title="Apps" icon="fa fa-fw fa-cubes" subnav="true">
|
||||||
|
<ul class="nav">
|
||||||
|
<li ><a href="apps">Overview</a></li>
|
||||||
|
<li class="active" ><a href="apps/edit/{{ctrl.current.type}}">Edit</a></li>
|
||||||
|
</ul>
|
||||||
|
</topnav>
|
||||||
|
|
||||||
|
<div class="page-container" style="background: transparent; border: 0;">
|
||||||
|
<div class="apps-side-box">
|
||||||
|
<div class="apps-side-box-logo" >
|
||||||
|
<img src="{{ctrl.appModel.info.logos.large}}">
|
||||||
|
</div>
|
||||||
|
<ul class="app-side-box-links">
|
||||||
|
<li>
|
||||||
|
By <a href="{{ctrl.appModel.info.author.url}}" class="external-link" target="_blank">{{ctrl.appModel.info.author.name}}</a>
|
||||||
|
</li>
|
||||||
|
<li ng-repeat="link in ctrl.appModel.info.links">
|
||||||
|
<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wide-margined" ng-init="ctrl.init()">
|
||||||
|
<h1>
|
||||||
|
{{ctrl.appModel.name}}
|
||||||
|
</h1>
|
||||||
|
<em>
|
||||||
|
{{ctrl.appModel.info.description}}<br>
|
||||||
|
<span style="small">
|
||||||
|
Version: {{ctrl.appModel.info.version}} Updated: {{ctrl.appModel.info.updated}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</em>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div class="form-inline">
|
||||||
|
<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
|
||||||
|
|
||||||
|
<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="ctrl.togglePinned()"></editor-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="simple-box">
|
||||||
|
<h3 class="simple-box-header">Included with app:</h3>
|
||||||
|
<div class="flex-container">
|
||||||
|
<div class="simple-box-body simple-box-column">
|
||||||
|
<div class="simple-box-column-header">
|
||||||
|
<i class="fa fa-th-large"></i>
|
||||||
|
Dashboards
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><em class="small">None</em></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="simple-box-body simple-box-column">
|
||||||
|
<div class="simple-box-column-header">
|
||||||
|
<i class="fa fa-line-chart"></i>
|
||||||
|
Panels
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><em class="small">None</em></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="simple-box-body simple-box-column">
|
||||||
|
<div class="simple-box-column-header">
|
||||||
|
<i class="fa fa-database"></i>
|
||||||
|
Datasources
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><em class="small">None</em></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="simple-box-body simple-box-column">
|
||||||
|
<div class="simple-box-column-header">
|
||||||
|
<i class="fa fa-files-o"></i>
|
||||||
|
Pages
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="page in ctrl.appModel.pages">
|
||||||
|
<a href="{{page.url}}" class="external-link">{{page.name}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="simple-box">
|
||||||
|
<h3 class="simple-box-header">Dependencies:</h3>
|
||||||
|
<div class="simple-box-body">
|
||||||
|
Grafana 2.6.x
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="simple-box">
|
||||||
|
<h3 class="simple-box-header">Configuration:</h3>
|
||||||
|
<div class="simple-box-body">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<app-config-loader></app-config-loader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
51
public/app/features/apps/partials/list.html
Normal file
51
public/app/features/apps/partials/list.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<topnav title="Apps" icon="fa fa-fw fa-cubes" subnav="true">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="active" ><a href="org/apps">Overview</a></li>
|
||||||
|
</ul>
|
||||||
|
</topnav>
|
||||||
|
|
||||||
|
<div class="page-container" style="background: transparent; border: 0;">
|
||||||
|
<div class="page-wide" ng-init="ctrl.init()">
|
||||||
|
<h2>Apps</h2>
|
||||||
|
|
||||||
|
<div ng-if="!ctrl.apps">
|
||||||
|
<em>No apps defined</em>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="filter-list">
|
||||||
|
<li ng-repeat="app in ctrl.apps">
|
||||||
|
<ul class="filter-list-card">
|
||||||
|
<li class="filter-list-card-image">
|
||||||
|
<img ng-src="{{app.info.logos.small}}">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="filter-list-card-controls">
|
||||||
|
<div class="filter-list-card-config">
|
||||||
|
<a href="apps/edit/{{app.appId}}">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="filter-list-card-title">
|
||||||
|
<a href="apps/edit/{{app.appId}}">
|
||||||
|
{{app.name}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span class="label label-info" ng-if="app.enabled">
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="label label-info" ng-if="app.pinned">
|
||||||
|
Pinned
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<span class="filter-list-card-status">
|
||||||
|
<span class="filter-list-card-state">Dashboards: 1</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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) {
|
$scope.showJsonEditor = function(evt, options) {
|
||||||
var editScope = $rootScope.$new();
|
var editScope = $rootScope.$new();
|
||||||
editScope.object = options.object;
|
editScope.object = options.object;
|
||||||
|
|||||||
4
public/app/features/datasources/all.js
Normal file
4
public/app/features/datasources/all.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
define([
|
||||||
|
'./list_ctrl',
|
||||||
|
'./edit_ctrl',
|
||||||
|
], function () {});
|
||||||
@@ -9,26 +9,14 @@ function (angular, _, config) {
|
|||||||
var module = angular.module('grafana.controllers');
|
var module = angular.module('grafana.controllers');
|
||||||
var datasourceTypes = [];
|
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) {
|
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: {}};
|
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.init = function() {
|
||||||
$scope.isNew = true;
|
$scope.isNew = true;
|
||||||
$scope.datasources = [];
|
$scope.datasources = [];
|
||||||
@@ -59,7 +47,7 @@ function (angular, _, config) {
|
|||||||
backendSrv.get('/api/datasources/' + id).then(function(ds) {
|
backendSrv.get('/api/datasources/' + id).then(function(ds) {
|
||||||
$scope.isNew = false;
|
$scope.isNew = false;
|
||||||
$scope.current = ds;
|
$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();
|
$scope.init();
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-include="datasourceMeta.partials.config" ng-if="datasourceMeta.partials.config"></div>
|
<datasource-custom-settings-view ds-meta="datasourceMeta" current="current"></datasource-custom-settings-view>
|
||||||
|
|
||||||
<div ng-if="testing" style="margin-top: 25px">
|
<div ng-if="testing" style="margin-top: 25px">
|
||||||
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||||
@@ -53,3 +53,5 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
define([
|
define([
|
||||||
'./datasourcesCtrl',
|
|
||||||
'./datasourceEditCtrl',
|
|
||||||
'./orgUsersCtrl',
|
'./orgUsersCtrl',
|
||||||
'./newOrgCtrl',
|
'./newOrgCtrl',
|
||||||
'./userInviteCtrl',
|
'./userInviteCtrl',
|
||||||
'./orgApiKeysCtrl',
|
'./orgApiKeysCtrl',
|
||||||
'./orgDetailsCtrl',
|
'./orgDetailsCtrl',
|
||||||
'./pluginsCtrl',
|
'../datasources/all',
|
||||||
'./pluginEditCtrl',
|
|
||||||
'./plugin_srv',
|
|
||||||
'./plugin_directive',
|
|
||||||
], function () {});
|
], function () {});
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<div>
|
|
||||||
{{current.type}} plugin does not have any additional config.
|
|
||||||
</div>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<topnav title="Plugins" icon="fa fa-fw fa-cubes" subnav="true">
|
|
||||||
<ul class="nav">
|
|
||||||
<li ><a href="plugins">Overview</a></li>
|
|
||||||
<li class="active" ><a href="plugins/edit/{{current.type}}">Edit</a></li>
|
|
||||||
</ul>
|
|
||||||
</topnav>
|
|
||||||
|
|
||||||
<div class="page-container">
|
|
||||||
<div class="page">
|
|
||||||
<h2>Edit Plugin</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<form name="editForm">
|
|
||||||
<div class="tight-form">
|
|
||||||
<ul class="tight-form-list">
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
Type
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<li>
|
|
||||||
<input type="text" disabled="disabled" class="input-xlarge tight-form-input" ng-model="current.type">
|
|
||||||
</li>
|
|
||||||
</li>
|
|
||||||
<li class="tight-form-item">
|
|
||||||
Default
|
|
||||||
<input class="cr1" id="current.enabled" type="checkbox" ng-model="current.enabled" ng-checked="current.enabled">
|
|
||||||
<label for="current.enabled" class="cr1"></label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<plugin-config-loader plugin="current"></plugin-config-loader>
|
|
||||||
<div class="pull-right" style="margin-top: 35px">
|
|
||||||
<button type="submit" class="btn btn-success" ng-click="update()">Save</button>
|
|
||||||
<a class="btn btn-inverse" href="plugins">Cancel</a>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<topnav title="Plugins" icon="fa fa-fw fa-cubes" subnav="true">
|
|
||||||
<ul class="nav">
|
|
||||||
<li class="active" ><a href="plugins">Overview</a></li>
|
|
||||||
</ul>
|
|
||||||
</topnav>
|
|
||||||
|
|
||||||
<div class="page-container">
|
|
||||||
<div class="page">
|
|
||||||
<h2>Plugins</h2>
|
|
||||||
|
|
||||||
<div ng-if="!plugins">
|
|
||||||
<em>No plugins defined</em>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="grafana-options-table" ng-if="plugins">
|
|
||||||
<tr>
|
|
||||||
<td><strong>Type</strong></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-repeat="(type, p) in plugins">
|
|
||||||
<td style="width:1%">
|
|
||||||
<i class="fa fa-cubes"></i>
|
|
||||||
{{p.type}}
|
|
||||||
</td>
|
|
||||||
<td style="width: 1%">
|
|
||||||
<a href="plugins/edit/{{p.type}}" class="btn btn-inverse btn-mini">
|
|
||||||
<i class="fa fa-edit"></i>
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td style="width: 1%">
|
|
||||||
Enabled
|
|
||||||
<input id="p.enabled" type="checkbox" ng-model="p.enabled" ng-checked="p.enabled" ng-change="update(p)">
|
|
||||||
<label for="p.enabled"></label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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) {
|
module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@@ -62,12 +89,26 @@ function (angular, $, config) {
|
|||||||
|
|
||||||
editorScope = options.scope.$new();
|
editorScope = options.scope.$new();
|
||||||
datasourceSrv.get(newVal).then(function(ds) {
|
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) {
|
module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
@@ -90,7 +131,7 @@ function (angular, $, config) {
|
|||||||
scope.target.refId = 'A';
|
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);
|
elem.append(panelEl);
|
||||||
$compile(panelEl)(editorScope);
|
$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) {
|
module.directive('panelResizer', function($rootScope) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
|
|||||||
@@ -34,13 +34,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li ng-if="!contextSrv.isSignedIn">
|
|
||||||
<a href="login" class="sidemenu-item" target="_self">
|
|
||||||
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-in"></i></span>
|
|
||||||
<span class="sidemenu-item-text">Sign in</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="sidemenu-system-section" ng-if="systemSection">
|
<li class="sidemenu-system-section" ng-if="systemSection">
|
||||||
<div class="sidemenu-system-section-inner">
|
<div class="sidemenu-system-section-inner">
|
||||||
<i class="fa fa-fw fa-cubes"></i>
|
<i class="fa fa-fw fa-cubes"></i>
|
||||||
@@ -52,8 +45,11 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li ng-repeat="item in mainLinks">
|
<li ng-repeat="item in mainLinks">
|
||||||
<a href="{{item.href}}" class="sidemenu-item sidemenu-main-link" target="{{item.target}}">
|
<a href="{{item.url}}" class="sidemenu-item sidemenu-main-link" target="{{item.target}}">
|
||||||
<span class="icon-circle sidemenu-icon"><i class="{{item.icon}}"></i></span>
|
<span class="icon-circle sidemenu-icon">
|
||||||
|
<i class="{{item.icon}}" ng-show="item.icon"></i>
|
||||||
|
<img ng-src="{{item.img}}" ng-show="item.img">
|
||||||
|
</span>
|
||||||
<span class="sidemenu-item-text">{{item.text}}</span>
|
<span class="sidemenu-item-text">{{item.text}}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
3
public/app/plugins/datasource/cloudwatch/datasource.d.ts
vendored
Normal file
3
public/app/plugins/datasource/cloudwatch/datasource.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare var Datasource: any;
|
||||||
|
export default Datasource;
|
||||||
|
|
||||||
@@ -4,24 +4,19 @@ define([
|
|||||||
'moment',
|
'moment',
|
||||||
'app/core/utils/datemath',
|
'app/core/utils/datemath',
|
||||||
'./query_ctrl',
|
'./query_ctrl',
|
||||||
'./directives',
|
|
||||||
],
|
],
|
||||||
function (angular, _, moment, dateMath) {
|
function (angular, _, moment, dateMath) {
|
||||||
'use strict';
|
'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) {
|
this.query = function(options) {
|
||||||
|
|
||||||
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) {
|
|
||||||
var start = convertToCloudWatchTime(options.range.from, false);
|
var start = convertToCloudWatchTime(options.range.from, false);
|
||||||
var end = convertToCloudWatchTime(options.range.to, true);
|
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({
|
return this.awsRequest({
|
||||||
region: query.region,
|
region: query.region,
|
||||||
action: 'GetMetricStatistics',
|
action: 'GetMetricStatistics',
|
||||||
@@ -88,15 +83,15 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.getRegions = function() {
|
this.getRegions = function() {
|
||||||
return this.awsRequest({action: '__GetRegions'});
|
return this.awsRequest({action: '__GetRegions'});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.getNamespaces = function() {
|
this.getNamespaces = function() {
|
||||||
return this.awsRequest({action: '__GetNamespaces'});
|
return this.awsRequest({action: '__GetNamespaces'});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.getMetrics = function(namespace) {
|
this.getMetrics = function(namespace) {
|
||||||
return this.awsRequest({
|
return this.awsRequest({
|
||||||
action: '__GetMetrics',
|
action: '__GetMetrics',
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -105,7 +100,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.getDimensionKeys = function(namespace) {
|
this.getDimensionKeys = function(namespace) {
|
||||||
return this.awsRequest({
|
return this.awsRequest({
|
||||||
action: '__GetDimensions',
|
action: '__GetDimensions',
|
||||||
parameters: {
|
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 = {
|
var request = {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(region),
|
||||||
action: 'ListMetrics',
|
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({
|
return this.awsRequest({
|
||||||
region: region,
|
region: region,
|
||||||
action: 'DescribeInstances',
|
action: 'DescribeInstances',
|
||||||
@@ -149,7 +144,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.metricFindQuery = function(query) {
|
this.metricFindQuery = function(query) {
|
||||||
var region;
|
var region;
|
||||||
var namespace;
|
var namespace;
|
||||||
var metricName;
|
var metricName;
|
||||||
@@ -210,7 +205,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
return $q.when([]);
|
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({
|
return this.awsRequest({
|
||||||
region: region,
|
region: region,
|
||||||
action: 'DescribeAlarmsForMetric',
|
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({
|
return this.awsRequest({
|
||||||
region: region,
|
region: region,
|
||||||
action: 'DescribeAlarmHistory',
|
action: 'DescribeAlarmHistory',
|
||||||
@@ -226,7 +221,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.annotationQuery = function(options) {
|
this.annotationQuery = function(options) {
|
||||||
var annotation = options.annotation;
|
var annotation = options.annotation;
|
||||||
var region = templateSrv.replace(annotation.region);
|
var region = templateSrv.replace(annotation.region);
|
||||||
var namespace = templateSrv.replace(annotation.namespace);
|
var namespace = templateSrv.replace(annotation.namespace);
|
||||||
@@ -278,7 +273,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
return d.promise;
|
return d.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.testDatasource = function() {
|
this.testDatasource = function() {
|
||||||
/* use billing metrics for test */
|
/* use billing metrics for test */
|
||||||
var region = this.defaultRegion;
|
var region = this.defaultRegion;
|
||||||
var namespace = 'AWS/Billing';
|
var namespace = 'AWS/Billing';
|
||||||
@@ -290,7 +285,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.awsRequest = function(data) {
|
this.awsRequest = function(data) {
|
||||||
var options = {
|
var options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: this.proxyUrl,
|
url: this.proxyUrl,
|
||||||
@@ -302,7 +297,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CloudWatchDatasource.prototype.getDefaultRegion = function() {
|
this.getDefaultRegion = function() {
|
||||||
return this.defaultRegion;
|
return this.defaultRegion;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -361,7 +356,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return CloudWatchDatasource;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
return CloudWatchDatasource;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'./datasource',
|
||||||
'./query_parameter_ctrl',
|
'./query_parameter_ctrl',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, CloudWatchDatasource) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.directives');
|
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
|
||||||
|
};
|
||||||
});
|
});
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "CloudWatch",
|
"name": "CloudWatch",
|
||||||
|
"id": "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"
|
|
||||||
},
|
|
||||||
|
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"annotations": true
|
"annotations": true
|
||||||
|
|||||||
@@ -3,25 +3,25 @@ import "../datasource";
|
|||||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import helpers from 'test/specs/helpers';
|
import helpers from 'test/specs/helpers';
|
||||||
|
import Datasource from "../datasource";
|
||||||
|
|
||||||
describe('CloudWatchDatasource', function() {
|
describe('CloudWatchDatasource', function() {
|
||||||
var ctx = new helpers.ServiceTestContext();
|
var ctx = new helpers.ServiceTestContext();
|
||||||
|
var instanceSettings = {
|
||||||
|
jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(angularMocks.module('grafana.core'));
|
beforeEach(angularMocks.module('grafana.core'));
|
||||||
beforeEach(angularMocks.module('grafana.services'));
|
beforeEach(angularMocks.module('grafana.services'));
|
||||||
beforeEach(angularMocks.module('grafana.controllers'));
|
beforeEach(angularMocks.module('grafana.controllers'));
|
||||||
|
|
||||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
||||||
beforeEach(ctx.createService('CloudWatchDatasource'));
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||||
ctx.ds = new ctx.service({
|
ctx.$q = $q;
|
||||||
jsonData: {
|
ctx.$httpBackend = $httpBackend;
|
||||||
defaultRegion: 'us-east-1',
|
ctx.$rootScope = $rootScope;
|
||||||
access: 'proxy'
|
ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings});
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('When performing CloudWatch query', function() {
|
describe('When performing CloudWatch query', function() {
|
||||||
var requestParams;
|
var requestParams;
|
||||||
|
|||||||
3
public/app/plugins/datasource/elasticsearch/datasource.d.ts
vendored
Normal file
3
public/app/plugins/datasource/elasticsearch/datasource.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare var Datasource: any;
|
||||||
|
export default Datasource;
|
||||||
|
|
||||||
@@ -7,33 +7,27 @@ define([
|
|||||||
'./index_pattern',
|
'./index_pattern',
|
||||||
'./elastic_response',
|
'./elastic_response',
|
||||||
'./query_ctrl',
|
'./query_ctrl',
|
||||||
'./directives'
|
|
||||||
],
|
],
|
||||||
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
|
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
|
||||||
'use strict';
|
'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) {
|
this._request = function(method, url, data) {
|
||||||
|
|
||||||
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) {
|
|
||||||
var options = {
|
var options = {
|
||||||
url: this.url + "/" + url,
|
url: this.url + "/" + url,
|
||||||
method: method,
|
method: method,
|
||||||
@@ -52,21 +46,21 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
return backendSrv.datasourceRequest(options);
|
return backendSrv.datasourceRequest(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
ElasticDatasource.prototype._get = function(url) {
|
this._get = function(url) {
|
||||||
return this._request('GET', this.indexPattern.getIndexForToday() + url)
|
return this._request('GET', this.indexPattern.getIndexForToday() + url)
|
||||||
.then(function(results) {
|
.then(function(results) {
|
||||||
return results.data;
|
return results.data;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ElasticDatasource.prototype._post = function(url, data) {
|
this._post = function(url, data) {
|
||||||
return this._request('POST', url, data)
|
return this._request('POST', url, data)
|
||||||
.then(function(results) {
|
.then(function(results) {
|
||||||
return results.data;
|
return results.data;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ElasticDatasource.prototype.annotationQuery = function(options) {
|
this.annotationQuery = function(options) {
|
||||||
var annotation = options.annotation;
|
var annotation = options.annotation;
|
||||||
var timeField = annotation.timeField || '@timestamp';
|
var timeField = annotation.timeField || '@timestamp';
|
||||||
var queryString = annotation.query || '*';
|
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 this._get('/_stats').then(function() {
|
||||||
return { status: "success", message: "Data source is working", title: "Success" };
|
return { status: "success", message: "Data source is working", title: "Success" };
|
||||||
}, function(err) {
|
}, 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};
|
var header = {search_type: searchType, "ignore_unavailable": true};
|
||||||
header.index = this.indexPattern.getIndexList(timeFrom, timeTo);
|
header.index = this.indexPattern.getIndexList(timeFrom, timeTo);
|
||||||
return angular.toJson(header);
|
return angular.toJson(header);
|
||||||
};
|
};
|
||||||
|
|
||||||
ElasticDatasource.prototype.query = function(options) {
|
this.query = function(options) {
|
||||||
var payload = "";
|
var payload = "";
|
||||||
var target;
|
var target;
|
||||||
var sentTargets = [];
|
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) {
|
return this._get('/_mapping').then(function(res) {
|
||||||
var fields = {};
|
var fields = {};
|
||||||
var typeMap = {
|
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 range = timeSrv.timeRange();
|
||||||
var header = this.getQueryHeader('count', range.from, range.to);
|
var header = this.getQueryHeader('count', range.from, range.to);
|
||||||
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
|
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 = templateSrv.replace(query);
|
||||||
query = angular.fromJson(query);
|
query = angular.fromJson(query);
|
||||||
if (!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)
|
return this._get('/dashboard/' + id)
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
return angular.fromJson(result._source.dashboard);
|
return angular.fromJson(result._source.dashboard);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ElasticDatasource.prototype.searchDashboards = function() {
|
this.searchDashboards = function() {
|
||||||
var query = {
|
var query = {
|
||||||
query: { query_string: { query: '*' } },
|
query: { query_string: { query: '*' } },
|
||||||
size: 10000,
|
size: 10000,
|
||||||
@@ -308,7 +302,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
return displayHits;
|
return displayHits;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return ElasticDatasource;
|
return ElasticDatasource;
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ function (angular) {
|
|||||||
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'};
|
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() {
|
module.directive('elasticMetricAgg', function() {
|
||||||
return {
|
return {
|
||||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
||||||
|
|||||||
38
public/app/plugins/datasource/elasticsearch/edit_view.ts
Normal file
38
public/app/plugins/datasource/elasticsearch/edit_view.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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;
|
||||||
60
public/app/plugins/datasource/elasticsearch/module.js
Normal file
60
public/app/plugins/datasource/elasticsearch/module.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<div ng-include="httpConfigPartialSrc"></div>
|
<datasource-http-settings></datasource-http-settings>
|
||||||
<br>
|
|
||||||
|
|
||||||
<h5>Elasticsearch details</h5>
|
<h5>Elasticsearch details</h5>
|
||||||
|
|
||||||
@@ -42,8 +41,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<br>
|
||||||
<h5>Default query settings</h5>
|
<h5>Default query settings</h5>
|
||||||
|
|
||||||
<div class="tight-form last">
|
<div class="tight-form last">
|
||||||
@@ -53,7 +52,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" class="input-medium tight-form-input input-xlarge" ng-model="current.jsonData.timeInterval"
|
<input type="text" class="input-medium tight-form-input input-xlarge" ng-model="current.jsonData.timeInterval"
|
||||||
spellcheck='false' placeholder="example: >10s">
|
spellcheck='false' placeholder="example: >10s">
|
||||||
</li>
|
</li>
|
||||||
<li class="tight-form-item">
|
<li class="tight-form-item">
|
||||||
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >10s'" data-placement="right"></i>
|
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >10s'" data-placement="right"></i>
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "Elasticsearch",
|
"name": "Elasticsearch",
|
||||||
|
"id": "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"
|
|
||||||
},
|
|
||||||
|
|
||||||
"defaultMatchFormat": "lucene",
|
"defaultMatchFormat": "lucene",
|
||||||
"annotations": true,
|
"annotations": true,
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
|
|
||||||
import "../datasource";
|
|
||||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import helpers from 'test/specs/helpers';
|
import helpers from 'test/specs/helpers';
|
||||||
|
import Datasource from "../datasource";
|
||||||
|
|
||||||
describe('ElasticDatasource', function() {
|
describe('ElasticDatasource', function() {
|
||||||
var ctx = new helpers.ServiceTestContext();
|
var ctx = new helpers.ServiceTestContext();
|
||||||
|
var instanceSettings: any = {jsonData: {}};
|
||||||
|
|
||||||
beforeEach(angularMocks.module('grafana.core'));
|
beforeEach(angularMocks.module('grafana.core'));
|
||||||
beforeEach(angularMocks.module('grafana.services'));
|
beforeEach(angularMocks.module('grafana.services'));
|
||||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
||||||
beforeEach(ctx.createService('ElasticDatasource'));
|
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||||
beforeEach(function() {
|
ctx.$q = $q;
|
||||||
ctx.ds = new ctx.service({jsonData: {}});
|
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() {
|
describe('When testing datasource with index pattern', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
ctx.ds = new ctx.service({
|
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}});
|
||||||
url: 'http://es.com',
|
|
||||||
index: '[asd-]YYYY.MM.DD',
|
|
||||||
jsonData: { interval: 'Daily' }
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should translate index pattern to current day', function() {
|
it('should translate index pattern to current day', function() {
|
||||||
@@ -44,11 +48,7 @@ describe('ElasticDatasource', function() {
|
|||||||
var requestOptions, parts, header;
|
var requestOptions, parts, header;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
ctx.ds = new ctx.service({
|
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}});
|
||||||
url: 'http://es.com',
|
|
||||||
index: '[asd-]YYYY.MM.DD',
|
|
||||||
jsonData: { interval: 'Daily' }
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = function(options) {
|
ctx.backendSrv.datasourceRequest = function(options) {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
@@ -83,7 +83,7 @@ describe('ElasticDatasource', function() {
|
|||||||
var requestOptions, parts, header;
|
var requestOptions, parts, header;
|
||||||
|
|
||||||
beforeEach(function() {
|
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) {
|
ctx.backendSrv.datasourceRequest = function(options) {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -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'};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "Grafana",
|
"name": "Grafana",
|
||||||
|
"id": "grafana",
|
||||||
|
|
||||||
"builtIn": true,
|
"builtIn": true,
|
||||||
|
|
||||||
"type": "grafana",
|
|
||||||
"serviceName": "GrafanaDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/grafana/datasource",
|
|
||||||
"metrics": true
|
"metrics": true
|
||||||
}
|
}
|
||||||
|
|||||||
3
public/app/plugins/datasource/graphite/datasource.d.ts
vendored
Normal file
3
public/app/plugins/datasource/graphite/datasource.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare var Datasource: any;
|
||||||
|
export default Datasource;
|
||||||
|
|
||||||
@@ -4,7 +4,6 @@ define([
|
|||||||
'jquery',
|
'jquery',
|
||||||
'app/core/config',
|
'app/core/config',
|
||||||
'app/core/utils/datemath',
|
'app/core/utils/datemath',
|
||||||
'./directives',
|
|
||||||
'./query_ctrl',
|
'./query_ctrl',
|
||||||
'./func_editor',
|
'./func_editor',
|
||||||
'./add_graphite_func',
|
'./add_graphite_func',
|
||||||
@@ -12,20 +11,16 @@ define([
|
|||||||
function (angular, _, $, config, dateMath) {
|
function (angular, _, $, config, dateMath) {
|
||||||
'use strict';
|
'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) {
|
this.query = function(options) {
|
||||||
|
|
||||||
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) {
|
|
||||||
try {
|
try {
|
||||||
var graphOptions = {
|
var graphOptions = {
|
||||||
from: this.translateTime(options.rangeRaw.from, false),
|
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 []; }
|
if (!result || !result.data) { return []; }
|
||||||
for (var i = 0; i < result.data.length; i++) {
|
for (var i = 0; i < result.data.length; i++) {
|
||||||
var series = result.data[i];
|
var series = result.data[i];
|
||||||
@@ -73,7 +68,7 @@ function (angular, _, $, config, dateMath) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphiteDatasource.prototype.annotationQuery = function(options) {
|
this.annotationQuery = function(options) {
|
||||||
// Graphite metric as annotation
|
// Graphite metric as annotation
|
||||||
if (options.annotation.target) {
|
if (options.annotation.target) {
|
||||||
var target = templateSrv.replace(options.annotation.target);
|
var target = templateSrv.replace(options.annotation.target);
|
||||||
@@ -85,50 +80,49 @@ function (angular, _, $, config, dateMath) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.query(graphiteQuery)
|
return this.query(graphiteQuery)
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
var list = [];
|
var list = [];
|
||||||
|
|
||||||
for (var i = 0; i < result.data.length; i++) {
|
for (var i = 0; i < result.data.length; i++) {
|
||||||
var target = result.data[i];
|
var target = result.data[i];
|
||||||
|
|
||||||
for (var y = 0; y < target.datapoints.length; y++) {
|
for (var y = 0; y < target.datapoints.length; y++) {
|
||||||
var datapoint = target.datapoints[y];
|
var datapoint = target.datapoints[y];
|
||||||
if (!datapoint[0]) { continue; }
|
if (!datapoint[0]) { continue; }
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
annotation: options.annotation,
|
annotation: options.annotation,
|
||||||
time: datapoint[1],
|
time: datapoint[1],
|
||||||
title: target.target
|
title: target.target
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Graphite event as annotation
|
// Graphite event as annotation
|
||||||
else {
|
else {
|
||||||
var tags = templateSrv.replace(options.annotation.tags);
|
var tags = templateSrv.replace(options.annotation.tags);
|
||||||
return this.events({range: options.rangeRaw, tags: tags})
|
return this.events({range: options.rangeRaw, tags: tags}).then(function(results) {
|
||||||
.then(function(results) {
|
var list = [];
|
||||||
var list = [];
|
for (var i = 0; i < results.data.length; i++) {
|
||||||
for (var i = 0; i < results.data.length; i++) {
|
var e = results.data[i];
|
||||||
var e = results.data[i];
|
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
annotation: options.annotation,
|
annotation: options.annotation,
|
||||||
time: e.when * 1000,
|
time: e.when * 1000,
|
||||||
title: e.what,
|
title: e.what,
|
||||||
tags: e.tags,
|
tags: e.tags,
|
||||||
text: e.data
|
text: e.data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphiteDatasource.prototype.events = function(options) {
|
this.events = function(options) {
|
||||||
try {
|
try {
|
||||||
var tags = '';
|
var tags = '';
|
||||||
if (options.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 (_.isString(date)) {
|
||||||
if (date === 'now') {
|
if (date === 'now') {
|
||||||
return 'now';
|
return 'now';
|
||||||
@@ -178,7 +172,7 @@ function (angular, _, $, config, dateMath) {
|
|||||||
return date.unix();
|
return date.unix();
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphiteDatasource.prototype.metricFindQuery = function(query) {
|
this.metricFindQuery = function(query) {
|
||||||
var interpolated;
|
var interpolated;
|
||||||
try {
|
try {
|
||||||
interpolated = encodeURIComponent(templateSrv.replace(query));
|
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 this.metricFindQuery('*').then(function () {
|
||||||
return { status: "success", message: "Data source is working", title: "Success" };
|
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 || ''} })
|
return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} })
|
||||||
.then(function(results) {
|
.then(function(results) {
|
||||||
return results.data.dashboards;
|
return results.data.dashboards;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphiteDatasource.prototype.loadDashboard = function(dashName) {
|
this.loadDashboard = function(dashName) {
|
||||||
return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(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) {
|
if (this.basicAuth || this.withCredentials) {
|
||||||
options.withCredentials = true;
|
options.withCredentials = true;
|
||||||
}
|
}
|
||||||
@@ -230,9 +224,9 @@ function (angular, _, $, config, dateMath) {
|
|||||||
return backendSrv.datasourceRequest(options);
|
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 graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
||||||
var clean_options = [], targets = {};
|
var clean_options = [], targets = {};
|
||||||
var target, targetValue, i;
|
var target, targetValue, i;
|
||||||
@@ -296,9 +290,7 @@ function (angular, _, $, config, dateMath) {
|
|||||||
|
|
||||||
return clean_options;
|
return clean_options;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return GraphiteDatasource;
|
return GraphiteDatasource;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'./datasource',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, GraphiteDatasource) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.directives');
|
var module = angular.module('grafana.directives');
|
||||||
@@ -18,4 +19,11 @@ function (angular) {
|
|||||||
return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'};
|
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,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
<div ng-include="httpConfigPartialSrc"></div>
|
<datasource-http-settings></datasource-http-settings>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
|
||||||
"name": "Graphite",
|
"name": "Graphite",
|
||||||
|
"type": "datasource",
|
||||||
"type": "graphite",
|
"id": "graphite",
|
||||||
"serviceName": "GraphiteDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/graphite/datasource",
|
|
||||||
|
|
||||||
"partials": {
|
|
||||||
"config": "app/plugins/datasource/graphite/partials/config.html"
|
|
||||||
},
|
|
||||||
|
|
||||||
"defaultMatchFormat": "glob",
|
"defaultMatchFormat": "glob",
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
|
|
||||||
import "../datasource";
|
|
||||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||||
import helpers from 'test/specs/helpers';
|
import helpers from 'test/specs/helpers';
|
||||||
|
import Datasource from "../datasource";
|
||||||
|
|
||||||
describe('graphiteDatasource', function() {
|
describe('graphiteDatasource', function() {
|
||||||
var ctx = new helpers.ServiceTestContext();
|
var ctx = new helpers.ServiceTestContext();
|
||||||
|
var instanceSettings: any = {url: ['']};
|
||||||
|
|
||||||
beforeEach(angularMocks.module('grafana.core'));
|
beforeEach(angularMocks.module('grafana.core'));
|
||||||
beforeEach(angularMocks.module('grafana.services'));
|
beforeEach(angularMocks.module('grafana.services'));
|
||||||
|
|
||||||
beforeEach(ctx.providePhase(['backendSrv']));
|
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() {
|
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() {
|
describe('When querying influxdb with one target using query editor target spec', function() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ define([
|
|||||||
'app/core/utils/datemath',
|
'app/core/utils/datemath',
|
||||||
'./influx_series',
|
'./influx_series',
|
||||||
'./influx_query',
|
'./influx_query',
|
||||||
'./directives',
|
|
||||||
'./query_ctrl',
|
'./query_ctrl',
|
||||||
],
|
],
|
||||||
function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
|
function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
|
||||||
@@ -12,27 +11,22 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
|
|||||||
|
|
||||||
InfluxQuery = InfluxQuery.default;
|
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.supportAnnotations = true;
|
||||||
this.type = 'influxdb';
|
this.supportMetrics = true;
|
||||||
this.urls = _.map(datasource.url.split(','), function(url) {
|
|
||||||
return url.trim();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.username = datasource.username;
|
this.query = function(options) {
|
||||||
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) {
|
|
||||||
var timeFilter = getTimeFilter(options);
|
var timeFilter = getTimeFilter(options);
|
||||||
var queryTargets = [];
|
var queryTargets = [];
|
||||||
var i, y;
|
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 timeFilter = getTimeFilter({rangeRaw: options.rangeRaw});
|
||||||
var query = options.annotation.query.replace('$timeFilter', timeFilter);
|
var query = options.annotation.query.replace('$timeFilter', timeFilter);
|
||||||
query = templateSrv.replace(query);
|
query = templateSrv.replace(query);
|
||||||
@@ -106,7 +100,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
InfluxDatasource.prototype.metricFindQuery = function (query) {
|
this.metricFindQuery = function (query) {
|
||||||
var interpolated;
|
var interpolated;
|
||||||
try {
|
try {
|
||||||
interpolated = templateSrv.replace(query);
|
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'});
|
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 this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(function () {
|
||||||
return { status: "success", message: "Data source is working", title: "Success" };
|
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 self = this;
|
||||||
|
|
||||||
var currentUrl = self.urls.shift();
|
var currentUrl = self.urls.shift();
|
||||||
@@ -219,9 +213,8 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
|
|||||||
}
|
}
|
||||||
return (date.valueOf() / 1000).toFixed(0) + 's';
|
return (date.valueOf() / 1000).toFixed(0) + 's';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return InfluxDatasource;
|
return InfluxDatasource;
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'./datasource',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, InfluxDatasource) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.directives');
|
var module = angular.module('grafana.directives');
|
||||||
@@ -18,4 +19,11 @@ function (angular) {
|
|||||||
return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'};
|
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
|
||||||
|
};
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<div ng-include="httpConfigPartialSrc"></div>
|
<datasource-http-settings></datasource-http-settings>
|
||||||
<br>
|
|
||||||
|
|
||||||
<h5>InfluxDB Details</h5>
|
<h5>InfluxDB Details</h5>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "InfluxDB 0.9.x",
|
"name": "InfluxDB 0.9.x",
|
||||||
|
"id": "influxdb",
|
||||||
"type": "influxdb",
|
|
||||||
"serviceName": "InfluxDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/influxdb/datasource",
|
|
||||||
|
|
||||||
"partials": {
|
|
||||||
"config": "app/plugins/datasource/influxdb/partials/config.html"
|
|
||||||
},
|
|
||||||
|
|
||||||
"defaultMatchFormat": "regex values",
|
"defaultMatchFormat": "regex values",
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
32
public/app/plugins/datasource/mixed/datasource.ts
Normal file
32
public/app/plugins/datasource/mixed/datasource.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "Mixed datasource",
|
"name": "Mixed datasource",
|
||||||
|
"id": "mixed",
|
||||||
|
|
||||||
"builtIn": true,
|
"builtIn": true,
|
||||||
"mixed": true,
|
"mixed": true,
|
||||||
|
|
||||||
"type": "mixed",
|
|
||||||
"serviceName": "MixedDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/mixed/datasource",
|
|
||||||
"metrics": true
|
"metrics": true
|
||||||
}
|
}
|
||||||
|
|||||||
3
public/app/plugins/datasource/opentsdb/datasource.d.ts
vendored
Normal file
3
public/app/plugins/datasource/opentsdb/datasource.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare var Datasource: any;
|
||||||
|
export default Datasource;
|
||||||
|
|
||||||
@@ -3,25 +3,19 @@ define([
|
|||||||
'lodash',
|
'lodash',
|
||||||
'app/core/utils/datemath',
|
'app/core/utils/datemath',
|
||||||
'moment',
|
'moment',
|
||||||
'./directives',
|
|
||||||
'./queryCtrl',
|
'./queryCtrl',
|
||||||
],
|
],
|
||||||
function (angular, _, dateMath) {
|
function (angular, _, dateMath) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.services');
|
function OpenTSDBDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||||
|
this.type = 'opentsdb';
|
||||||
module.factory('OpenTSDBDatasource', function($q, backendSrv, templateSrv) {
|
this.url = instanceSettings.url;
|
||||||
|
this.name = instanceSettings.name;
|
||||||
function OpenTSDBDatasource(datasource) {
|
this.supportMetrics = true;
|
||||||
this.type = 'opentsdb';
|
|
||||||
this.url = datasource.url;
|
|
||||||
this.name = datasource.name;
|
|
||||||
this.supportMetrics = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called once per panel (graph)
|
// Called once per panel (graph)
|
||||||
OpenTSDBDatasource.prototype.query = function(options) {
|
this.query = function(options) {
|
||||||
var start = convertToTSDBTime(options.rangeRaw.from, false);
|
var start = convertToTSDBTime(options.rangeRaw.from, false);
|
||||||
var end = convertToTSDBTime(options.rangeRaw.to, true);
|
var end = convertToTSDBTime(options.rangeRaw.to, true);
|
||||||
var qs = [];
|
var qs = [];
|
||||||
@@ -60,7 +54,7 @@ function (angular, _, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenTSDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) {
|
this.performTimeSeriesQuery = function(queries, start, end) {
|
||||||
var reqBody = {
|
var reqBody = {
|
||||||
start: start,
|
start: start,
|
||||||
queries: queries
|
queries: queries
|
||||||
@@ -80,13 +74,13 @@ function (angular, _, dateMath) {
|
|||||||
return backendSrv.datasourceRequest(options);
|
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 this._get('/api/suggest', {type: type, q: query, max: 1000}).then(function(result) {
|
||||||
return result.data;
|
return result.data;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenTSDBDatasource.prototype._performMetricKeyValueLookup = function(metric, key) {
|
this._performMetricKeyValueLookup = function(metric, key) {
|
||||||
if(!metric || !key) {
|
if(!metric || !key) {
|
||||||
return $q.when([]);
|
return $q.when([]);
|
||||||
}
|
}
|
||||||
@@ -105,7 +99,7 @@ function (angular, _, dateMath) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenTSDBDatasource.prototype._performMetricKeyLookup = function(metric) {
|
this._performMetricKeyLookup = function(metric) {
|
||||||
if(!metric) { return $q.when([]); }
|
if(!metric) { return $q.when([]); }
|
||||||
|
|
||||||
return this._get('/api/search/lookup', {m: metric, limit: 1000}).then(function(result) {
|
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({
|
return backendSrv.datasourceRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: this.url + relativeUrl,
|
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([]); }
|
if (!query) { return $q.when([]); }
|
||||||
|
|
||||||
var interpolated;
|
var interpolated;
|
||||||
@@ -181,14 +175,14 @@ function (angular, _, dateMath) {
|
|||||||
return $q.when([]);
|
return $q.when([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenTSDBDatasource.prototype.testDatasource = function() {
|
this.testDatasource = function() {
|
||||||
return this._performSuggestQuery('cpu', 'metrics').then(function () {
|
return this._performSuggestQuery('cpu', 'metrics').then(function () {
|
||||||
return { status: "success", message: "Data source is working", title: "Success" };
|
return { status: "success", message: "Data source is working", title: "Success" };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var aggregatorsPromise = null;
|
var aggregatorsPromise = null;
|
||||||
OpenTSDBDatasource.prototype.getAggregators = function() {
|
this.getAggregators = function() {
|
||||||
if (aggregatorsPromise) { return aggregatorsPromise; }
|
if (aggregatorsPromise) { return aggregatorsPromise; }
|
||||||
|
|
||||||
aggregatorsPromise = this._get('/api/aggregators').then(function(result) {
|
aggregatorsPromise = this._get('/api/aggregators').then(function(result) {
|
||||||
@@ -311,7 +305,7 @@ function (angular, _, dateMath) {
|
|||||||
return date.valueOf();
|
return date.valueOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
return OpenTSDBDatasource;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
return OpenTSDBDatasource;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'./datasource',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, OpenTsDatasource) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.directives');
|
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
|
||||||
|
};
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
<div ng-include="httpConfigPartialSrc"></div>
|
<datasource-http-settings></datasource-http-settings>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "OpenTSDB",
|
"name": "OpenTSDB",
|
||||||
|
"id": "opentsdb",
|
||||||
"type": "opentsdb",
|
|
||||||
"serviceName": "OpenTSDBDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/opentsdb/datasource",
|
|
||||||
|
|
||||||
"partials": {
|
|
||||||
"config": "app/plugins/datasource/opentsdb/partials/config.html"
|
|
||||||
},
|
|
||||||
|
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"defaultMatchFormat": "pipe"
|
"defaultMatchFormat": "pipe"
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
3
public/app/plugins/datasource/prometheus/datasource.d.ts
vendored
Normal file
3
public/app/plugins/datasource/prometheus/datasource.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare var Datasource: any;
|
||||||
|
export default Datasource;
|
||||||
|
|
||||||
@@ -3,31 +3,25 @@ define([
|
|||||||
'lodash',
|
'lodash',
|
||||||
'moment',
|
'moment',
|
||||||
'app/core/utils/datemath',
|
'app/core/utils/datemath',
|
||||||
'./directives',
|
|
||||||
'./query_ctrl',
|
'./query_ctrl',
|
||||||
],
|
],
|
||||||
function (angular, _, moment, dateMath) {
|
function (angular, _, moment, dateMath) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.services');
|
|
||||||
|
|
||||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
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._request = function(method, url) {
|
||||||
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) {
|
|
||||||
var options = {
|
var options = {
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: method
|
method: method
|
||||||
@@ -46,7 +40,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Called once per panel (graph)
|
// Called once per panel (graph)
|
||||||
PrometheusDatasource.prototype.query = function(options) {
|
this.query = function(options) {
|
||||||
var start = getPrometheusTime(options.range.from, false);
|
var start = getPrometheusTime(options.range.from, false);
|
||||||
var end = getPrometheusTime(options.range.to, true);
|
var end = getPrometheusTime(options.range.to, true);
|
||||||
|
|
||||||
@@ -86,31 +80,31 @@ function (angular, _, moment, dateMath) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
return $q.all(allQueryPromise)
|
return $q.all(allQueryPromise)
|
||||||
.then(function(allResponse) {
|
.then(function(allResponse) {
|
||||||
var result = [];
|
var result = [];
|
||||||
|
|
||||||
_.each(allResponse, function(response, index) {
|
_.each(allResponse, function(response, index) {
|
||||||
if (response.status === 'error') {
|
if (response.status === 'error') {
|
||||||
self.lastErrors.query = response.error;
|
self.lastErrors.query = response.error;
|
||||||
throw response.error;
|
throw response.error;
|
||||||
}
|
}
|
||||||
delete self.lastErrors.query;
|
delete self.lastErrors.query;
|
||||||
|
|
||||||
_.each(response.data.data.result, function(metricData) {
|
_.each(response.data.data.result, function(metricData) {
|
||||||
result.push(transformMetricData(metricData, options.targets[index]));
|
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;
|
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
|
||||||
return this._request('GET', url);
|
return this._request('GET', url);
|
||||||
};
|
};
|
||||||
|
|
||||||
PrometheusDatasource.prototype.performSuggestQuery = function(query) {
|
this.performSuggestQuery = function(query) {
|
||||||
var url = '/api/v1/label/__name__/values';
|
var url = '/api/v1/label/__name__/values';
|
||||||
|
|
||||||
return this._request('GET', url).then(function(result) {
|
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([]); }
|
if (!query) { return $q.when([]); }
|
||||||
|
|
||||||
var interpolated;
|
var interpolated;
|
||||||
@@ -196,7 +190,7 @@ function (angular, _, moment, dateMath) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PrometheusDatasource.prototype.testDatasource = function() {
|
this.testDatasource = function() {
|
||||||
return this.metricFindQuery('metrics(.*)').then(function() {
|
return this.metricFindQuery('metrics(.*)').then(function() {
|
||||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
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 (date.valueOf() / 1000).toFixed(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return PrometheusDatasource;
|
return PrometheusDatasource;
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'./datasource',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, PromDatasource) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.directives');
|
var module = angular.module('grafana.directives');
|
||||||
@@ -10,4 +11,11 @@ function (angular) {
|
|||||||
return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'};
|
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
|
||||||
|
};
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
<div ng-include="httpConfigPartialSrc"></div>
|
<datasource-http-settings></datasource-http-settings>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pluginType": "datasource",
|
"type": "datasource",
|
||||||
"name": "Prometheus",
|
"name": "Prometheus",
|
||||||
|
"id": "prometheus",
|
||||||
"type": "prometheus",
|
|
||||||
"serviceName": "PrometheusDatasource",
|
|
||||||
|
|
||||||
"module": "app/plugins/datasource/prometheus/datasource",
|
|
||||||
|
|
||||||
"partials": {
|
|
||||||
"config": "app/plugins/datasource/prometheus/partials/config.html"
|
|
||||||
},
|
|
||||||
|
|
||||||
"metrics": true
|
"metrics": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import '../datasource';
|
|
||||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import helpers from 'test/specs/helpers';
|
import helpers from 'test/specs/helpers';
|
||||||
|
import Datasource from '../datasource';
|
||||||
|
|
||||||
describe('PrometheusDatasource', function() {
|
describe('PrometheusDatasource', function() {
|
||||||
|
|
||||||
var ctx = new helpers.ServiceTestContext();
|
var ctx = new helpers.ServiceTestContext();
|
||||||
|
var instanceSettings = {url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' };
|
||||||
|
|
||||||
beforeEach(angularMocks.module('grafana.core'));
|
beforeEach(angularMocks.module('grafana.core'));
|
||||||
beforeEach(angularMocks.module('grafana.services'));
|
beforeEach(angularMocks.module('grafana.services'));
|
||||||
beforeEach(ctx.createService('PrometheusDatasource'));
|
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||||
beforeEach(function() {
|
ctx.$q = $q;
|
||||||
ctx.ds = new ctx.service({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' });
|
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() {
|
describe('When querying prometheus with one target using query editor target spec', function() {
|
||||||
var results;
|
var results;
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
define([
|
|
||||||
'angular',
|
|
||||||
],
|
|
||||||
function (angular) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var module = angular.module('grafana.services');
|
|
||||||
|
|
||||||
module.factory('SqlDatasource', function() {
|
|
||||||
|
|
||||||
function SqlDatasource() {
|
|
||||||
}
|
|
||||||
|
|
||||||
return SqlDatasource;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<h2>SQL Options</h2>
|
|
||||||
|
|
||||||
<div class="tight-form">
|
|
||||||
<ul class="tight-form-list">
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
DB Type
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<select class="input-medium tight-form-input" ng-model="current.jsonData.dbType" ng-options="f for f in ['sqlite3','mysql','postgres']"></select>
|
|
||||||
</li>
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
Host
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input type="text" class="tight-form-input input-medium" ng-model='current.jsonData.host' placeholder="localhost:3306">
|
|
||||||
</li>
|
|
||||||
<li class="tight-form-item" ng-if="current.jsonData.dbType === 'postgres'">
|
|
||||||
SSL
|
|
||||||
<input class="cr1" id="jsonData.ssl" type="checkbox" ng-model="current.jsonData.ssl" ng-checked="current.jsonData.ssl">
|
|
||||||
<label for="jsonData.ssl" class="cr1"></label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tight-form">
|
|
||||||
<ul class="tight-form-list">
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
Database
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input type="text" class="tight-form-input input-medium" ng-model='current.database' placeholder="">
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tight-form">
|
|
||||||
<ul class="tight-form-list">
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
User
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input type="text" class="tight-form-input input-medium" ng-model='current.user' placeholder="">
|
|
||||||
</li>
|
|
||||||
<li class="tight-form-item" style="width: 80px">
|
|
||||||
Password
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input type="password" class="tight-form-input input-medium" ng-model='current.password' placeholder="">
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
<div class="fluid-row" style="margin-top: 20px">
|
|
||||||
<div class="span2"></div>
|
|
||||||
<div class="grafana-info-box span8">
|
|
||||||
<h5>Test graph</h5>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
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 <strong>Add query</strong> button.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="span2"></div>
|
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user