rebase against master

This commit is contained in:
benrubson 2016-02-28 10:43:11 +01:00
commit f4037667fa
154 changed files with 3416 additions and 4101 deletions

1
.gitignore vendored
View File

@ -31,6 +31,7 @@ public/css/*.min.css
conf/custom.ini conf/custom.ini
fig.yml fig.yml
docker-compose.yml
profile.cov profile.cov
/grafana /grafana
.notouch .notouch

View File

@ -7,6 +7,7 @@
* **Snapshots UI**: Dashboard snapshots list can be managed through UI, closes[#1984](https://github.com/grafana/grafana/issues/1984) * **Snapshots UI**: Dashboard snapshots list can be managed through UI, closes[#1984](https://github.com/grafana/grafana/issues/1984)
* **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883) * **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883)
* **Cli**: New cli tool for downloading and updating plugins * **Cli**: New cli tool for downloading and updating plugins
* **Annotations**: Annotations can now contain links that can be clicked (you can navigate on to annotation popovers), closes [#1588](https://github.com/grafana/grafana/issues/1588)
### Breaking changes ### Breaking changes
* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info. * **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.

View File

@ -7,7 +7,7 @@ template_dir=templates
grafana_config_file=conf.tmp grafana_config_file=conf.tmp
grafana_config=config grafana_config=config
fig_file=fig.yml fig_file=docker-compose.yml
fig_config=fig fig_config=fig
if [ "$#" == 0 ]; then if [ "$#" == 0 ]; then

View File

@ -122,8 +122,8 @@ To configure Grafana add a configuration file named `custom.ini` to the
`conf` folder and override any of the settings defined in `conf` folder and override any of the settings defined in
`conf/defaults.ini`. `conf/defaults.ini`.
Start Grafana by executing `./grafana-server web`. The `grafana-server` binary needs Start Grafana by executing `./bin/grafana-server web`. The `grafana-server`
the working directory to be the root install directory (where the binary binary needs the working directory to be the root install directory (where the
and the `public` folder is located). binary and the `public` folder is located).

View File

@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) {
r.Patch("/invites/:code/revoke", wrap(RevokeInvite)) r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
// apps // apps
r.Get("/apps", wrap(GetOrgAppsList)) r.Get("/plugins", wrap(GetPluginList))
r.Get("/apps/:appId/settings", wrap(GetAppSettingsById)) r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings)) r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
}, reqOrgAdmin) }, reqOrgAdmin)
// create new org // create new org

View File

@ -1,59 +0,0 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
func GetOrgAppsList(c *middleware.Context) Response {
orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to list of apps", err)
}
result := make([]*dtos.AppSettings, 0)
for _, app := range plugins.Apps {
orgApp := orgApps[app.Id]
result = append(result, dtos.NewAppSettingsDto(app, orgApp))
}
return Json(200, result)
}
func GetAppSettingsById(c *middleware.Context) Response {
appId := c.Params(":appId")
if pluginDef, exists := plugins.Apps[appId]; !exists {
return ApiError(404, "PluginId not found, no installed plugin with that id", nil)
} else {
orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to get org app settings ", nil)
}
orgApp := orgApps[appId]
return Json(200, dtos.NewAppSettingsDto(pluginDef, orgApp))
}
}
func UpdateAppSettings(c *middleware.Context, cmd m.UpdateAppSettingsCmd) Response {
appId := c.Params(":appId")
cmd.OrgId = c.OrgId
cmd.AppId = appId
if _, ok := plugins.Apps[cmd.AppId]; !ok {
return ApiError(404, "App type not installed.", nil)
}
err := bus.Dispatch(&cmd)
if err != nil {
return ApiError(500, "Failed to update App Plugin", err)
}
return ApiSuccess("App updated")
}

View File

@ -33,6 +33,7 @@ func init() {
actionHandlers = map[string]actionHandler{ actionHandlers = map[string]actionHandler{
"GetMetricStatistics": handleGetMetricStatistics, "GetMetricStatistics": handleGetMetricStatistics,
"ListMetrics": handleListMetrics, "ListMetrics": handleListMetrics,
"DescribeAlarms": handleDescribeAlarms,
"DescribeAlarmsForMetric": handleDescribeAlarmsForMetric, "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
"DescribeAlarmHistory": handleDescribeAlarmHistory, "DescribeAlarmHistory": handleDescribeAlarmHistory,
"DescribeInstances": handleDescribeInstances, "DescribeInstances": handleDescribeInstances,
@ -142,6 +143,49 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
c.JSON(200, resp) c.JSON(200, resp)
} }
func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{
Region: aws.String(req.Region),
Credentials: getCredentials(req.DataSource.Database),
}
svc := cloudwatch.New(session.New(cfg), cfg)
reqParam := &struct {
Parameters struct {
ActionPrefix string `json:"actionPrefix"`
AlarmNamePrefix string `json:"alarmNamePrefix"`
AlarmNames []*string `json:"alarmNames"`
StateValue string `json:"stateValue"`
} `json:"parameters"`
}{}
json.Unmarshal(req.Body, reqParam)
params := &cloudwatch.DescribeAlarmsInput{
MaxRecords: aws.Int64(100),
}
if reqParam.Parameters.ActionPrefix != "" {
params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix)
}
if reqParam.Parameters.AlarmNamePrefix != "" {
params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix)
}
if len(reqParam.Parameters.AlarmNames) != 0 {
params.AlarmNames = reqParam.Parameters.AlarmNames
}
if reqParam.Parameters.StateValue != "" {
params.StateValue = aws.String(reqParam.Parameters.StateValue)
}
resp, err := svc.DescribeAlarms(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),

View File

@ -64,20 +64,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
return &httputil.ReverseProxy{Director: director} return &httputil.ReverseProxy{Director: director}
} }
var dsMap map[int64]*m.DataSource = make(map[int64]*m.DataSource)
func getDatasource(id int64, orgId int64) (*m.DataSource, error) { func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
// ds, exists := dsMap[id]
// if exists && ds.OrgId == orgId {
// return ds, nil
// }
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId} query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
} }
dsMap[id] = &query.Result
return &query.Result, nil return &query.Result, nil
} }

View File

@ -1,40 +0,0 @@
package dtos
import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
type AppSettings struct {
Name string `json:"name"`
AppId string `json:"appId"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"`
Pages []*plugins.AppPluginPage `json:"pages"`
Includes []*plugins.AppIncludeInfo `json:"includes"`
JsonData map[string]interface{} `json:"jsonData"`
}
func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
dto := &AppSettings{
AppId: def.Id,
Name: def.Name,
Info: &def.Info,
Module: def.Module,
BaseUrl: def.BaseUrl,
Pages: def.Pages,
Includes: def.Includes,
}
if data != nil {
dto.Enabled = data.Enabled
dto.Pinned = data.Pinned
dto.Info = &def.Info
dto.JsonData = data.JsonData
}
return dto
}

View File

@ -16,9 +16,10 @@ type PluginCss struct {
} }
type NavLink struct { type NavLink struct {
Text string `json:"text"` Text string `json:"text,omitempty"`
Icon string `json:"icon"` Icon string `json:"icon,omitempty"`
Img string `json:"img"` Img string `json:"img,omitempty"`
Url string `json:"url"` Url string `json:"url,omitempty"`
Children []*NavLink `json:"children"` Divider bool `json:"divider,omitempty"`
Children []*NavLink `json:"children,omitempty"`
} }

26
pkg/api/dtos/plugins.go Normal file
View File

@ -0,0 +1,26 @@
package dtos
import "github.com/grafana/grafana/pkg/plugins"
type PluginSetting struct {
Name string `json:"name"`
Type string `json:"type"`
PluginId string `json:"pluginId"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"`
Pages []*plugins.AppPluginPage `json:"pages"`
Includes []*plugins.AppIncludeInfo `json:"includes"`
JsonData map[string]interface{} `json:"jsonData"`
}
type PluginListItem struct {
Name string `json:"name"`
Type string `json:"type"`
PluginId string `json:"pluginId"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
}

View File

@ -53,15 +53,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Icon: "icon-gf icon-gf-dashboard", Icon: "icon-gf icon-gf-dashboard",
Url: setting.AppSubUrl + "/", Url: setting.AppSubUrl + "/",
Children: []*dtos.NavLink{ Children: []*dtos.NavLink{
{Text: "Home dashboard", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/"}, {Text: "Home", Url: setting.AppSubUrl + "/"},
{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"}, {Text: "Playlists", Url: setting.AppSubUrl + "/playlists"},
{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"}, {Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots"},
{Divider: true},
{Text: "New", Url: setting.AppSubUrl + "/dashboard/new"},
{Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"},
}, },
}) })
// data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"})
// data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"})
if c.OrgRole == m.ROLE_ADMIN { if c.OrgRole == m.ROLE_ADMIN {
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Data Sources", Text: "Data Sources",
@ -72,7 +72,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Plugins", Text: "Plugins",
Icon: "icon-gf icon-gf-apps", Icon: "icon-gf icon-gf-apps",
Url: setting.AppSubUrl + "/apps", Url: setting.AppSubUrl + "/plugins",
}) })
} }

88
pkg/api/plugin_setting.go Normal file
View File

@ -0,0 +1,88 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
func GetPluginList(c *middleware.Context) Response {
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to get list of plugins", err)
}
result := make([]*dtos.PluginListItem, 0)
for _, pluginDef := range plugins.Plugins {
listItem := &dtos.PluginListItem{
PluginId: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
listItem.Enabled = pluginSetting.Enabled
listItem.Pinned = pluginSetting.Pinned
}
result = append(result, listItem)
}
return Json(200, result)
}
func GetPluginSettingById(c *middleware.Context) Response {
pluginId := c.Params(":pluginId")
if def, exists := plugins.Plugins[pluginId]; !exists {
return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
} else {
dto := &dtos.PluginSetting{
Type: def.Type,
PluginId: def.Id,
Name: def.Name,
Info: &def.Info,
}
if app, exists := plugins.Apps[pluginId]; exists {
dto.Pages = app.Pages
dto.Includes = app.Includes
dto.BaseUrl = app.BaseUrl
dto.Module = app.Module
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrPluginSettingNotFound {
return ApiError(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return Json(200, dto)
}
}
func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Response {
pluginId := c.Params(":pluginId")
cmd.OrgId = c.OrgId
cmd.PluginId = pluginId
if _, ok := plugins.Apps[cmd.PluginId]; !ok {
return ApiError(404, "Plugin not installed.", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update plugin setting", err)
}
return ApiSuccess("Plugin settings updated")
}

View File

@ -26,7 +26,7 @@ type templateData struct {
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) { func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
result := http.Header{} result := http.Header{}
query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId} query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err

View File

@ -22,8 +22,8 @@ func TestPluginProxy(t *testing.T) {
setting.SecretKey = "password" setting.SecretKey = "password"
bus.AddHandler("test", func(query *m.GetAppSettingByAppIdQuery) error { bus.AddHandler("test", func(query *m.GetPluginSettingByIdQuery) error {
query.Result = &m.AppSettings{ query.Result = &m.PluginSetting{
SecureJsonData: map[string][]byte{ SecureJsonData: map[string][]byte{
"key": util.Encrypt([]byte("123"), "password"), "key": util.Encrypt([]byte("123"), "password"),
}, },

View File

@ -9,12 +9,12 @@ import (
) )
var ( var (
ErrAppSettingNotFound = errors.New("AppSetting not found") ErrPluginSettingNotFound = errors.New("Plugin setting not found")
) )
type AppSettings struct { type PluginSetting struct {
Id int64 Id int64
AppId string PluginId string
OrgId int64 OrgId int64
Enabled bool Enabled bool
Pinned bool Pinned bool
@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string {
// COMMANDS // COMMANDS
// Also acts as api DTO // Also acts as api DTO
type UpdateAppSettingsCmd struct { type UpdatePluginSettingCmd struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"` Pinned bool `json:"pinned"`
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"` SecureJsonData map[string]string `json:"secureJsonData"`
AppId string `json:"-"` PluginId string `json:"-"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
} }
func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData { func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
encrypted := make(SecureJsonData) encrypted := make(SecureJsonData)
for key, data := range cmd.SecureJsonData { for key, data := range cmd.SecureJsonData {
encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey) encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey)
@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
// --------------------- // ---------------------
// QUERIES // QUERIES
type GetAppSettingsQuery struct { type GetPluginSettingsQuery struct {
OrgId int64 OrgId int64
Result []*AppSettings Result []*PluginSetting
} }
type GetAppSettingByAppIdQuery struct { type GetPluginSettingByIdQuery struct {
AppId string PluginId string
OrgId int64 OrgId int64
Result *AppSettings Result *PluginSetting
} }

View File

@ -56,6 +56,10 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
} }
func evalRelativePluginUrlPath(pathStr string, pluginId string) string { func evalRelativePluginUrlPath(pathStr string, pluginId string) string {
if pathStr == "" {
return ""
}
u, _ := url.Parse(pathStr) u, _ := url.Parse(pathStr)
if u.IsAbs() { if u.IsAbs() {
return pathStr return pathStr

View File

@ -5,44 +5,40 @@ import (
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) { func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) {
query := m.GetAppSettingsQuery{OrgId: orgId} query := m.GetPluginSettingsQuery{OrgId: orgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
} }
orgAppsMap := make(map[string]*m.AppSettings) pluginMap := make(map[string]*m.PluginSetting)
for _, orgApp := range query.Result { for _, plug := range query.Result {
orgAppsMap[orgApp.AppId] = orgApp pluginMap[plug.PluginId] = plug
} }
return orgAppsMap, nil return pluginMap, nil
} }
func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
enabledPlugins := NewEnabledPlugins() enabledPlugins := NewEnabledPlugins()
orgApps, err := GetOrgAppSettings(orgId) orgPlugins, err := GetPluginSettings(orgId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
enabledApps := make(map[string]bool) enabledApps := make(map[string]bool)
for appId, installedApp := range Apps { for pluginId, app := range Apps {
var app AppPlugin
app = *installedApp
// check if the app is stored in the DB for this org and if so, use the if b, ok := orgPlugins[pluginId]; ok {
// state stored there.
if b, ok := orgApps[appId]; ok {
app.Enabled = b.Enabled app.Enabled = b.Enabled
app.Pinned = b.Pinned app.Pinned = b.Pinned
} }
if app.Enabled { if app.Enabled {
enabledApps[app.Id] = true enabledApps[pluginId] = true
enabledPlugins.Apps = append(enabledPlugins.Apps, &app) enabledPlugins.Apps = append(enabledPlugins.Apps, app)
} }
} }

View File

@ -1,70 +0,0 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
func init() {
bus.AddHandler("sql", GetAppSettings)
bus.AddHandler("sql", GetAppSettingByAppId)
bus.AddHandler("sql", UpdateAppSettings)
}
func GetAppSettings(query *m.GetAppSettingsQuery) error {
sess := x.Where("org_id=?", query.OrgId)
query.Result = make([]*m.AppSettings, 0)
return sess.Find(&query.Result)
}
func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error {
appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId}
has, err := x.Get(&appSetting)
if err != nil {
return err
} else if has == false {
return m.ErrAppSettingNotFound
}
query.Result = &appSetting
return nil
}
func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
return inTransaction2(func(sess *session) error {
var app m.AppSettings
exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app)
sess.UseBool("enabled")
sess.UseBool("pinned")
if !exists {
app = m.AppSettings{
AppId: cmd.AppId,
OrgId: cmd.OrgId,
Enabled: cmd.Enabled,
Pinned: cmd.Pinned,
JsonData: cmd.JsonData,
SecureJsonData: cmd.GetEncryptedJsonData(),
Created: time.Now(),
Updated: time.Now(),
}
_, err = sess.Insert(&app)
return err
} else {
for key, data := range cmd.SecureJsonData {
app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
}
app.SecureJsonData = cmd.GetEncryptedJsonData()
app.Updated = time.Now()
app.Enabled = cmd.Enabled
app.JsonData = cmd.JsonData
app.Pinned = cmd.Pinned
_, err = sess.Id(app.Id).Update(&app)
return err
}
})
}

View File

@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addAppSettingsMigration(mg *Migrator) { func addAppSettingsMigration(mg *Migrator) {
appSettingsV2 := Table{ pluginSettingTable := Table{
Name: "app_settings", Name: "plugin_setting",
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: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "enabled", Type: DB_Bool, Nullable: false}, {Name: "enabled", Type: DB_Bool, Nullable: false},
{Name: "pinned", 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},
@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) {
{Name: "updated", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false},
}, },
Indices: []*Index{ Indices: []*Index{
{Cols: []string{"org_id", "app_id"}, Type: UniqueIndex}, {Cols: []string{"org_id", "plugin_id"}, Type: UniqueIndex},
}, },
} }
mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings")) mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable))
mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2))
//------- indexes ------------------ //------- indexes ------------------
addTableIndicesMigrations(mg, "v3", appSettingsV2) addTableIndicesMigrations(mg, "v1", pluginSettingTable)
} }

View File

@ -0,0 +1,70 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
func init() {
bus.AddHandler("sql", GetPluginSettings)
bus.AddHandler("sql", GetPluginSettingById)
bus.AddHandler("sql", UpdatePluginSetting)
}
func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
sess := x.Where("org_id=?", query.OrgId)
query.Result = make([]*m.PluginSetting, 0)
return sess.Find(&query.Result)
}
func GetPluginSettingById(query *m.GetPluginSettingByIdQuery) error {
pluginSetting := m.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId}
has, err := x.Get(&pluginSetting)
if err != nil {
return err
} else if has == false {
return m.ErrPluginSettingNotFound
}
query.Result = &pluginSetting
return nil
}
func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
return inTransaction2(func(sess *session) error {
var pluginSetting m.PluginSetting
exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
sess.UseBool("enabled")
sess.UseBool("pinned")
if !exists {
pluginSetting = m.PluginSetting{
PluginId: cmd.PluginId,
OrgId: cmd.OrgId,
Enabled: cmd.Enabled,
Pinned: cmd.Pinned,
JsonData: cmd.JsonData,
SecureJsonData: cmd.GetEncryptedJsonData(),
Created: time.Now(),
Updated: time.Now(),
}
_, err = sess.Insert(&pluginSetting)
return err
} else {
for key, data := range cmd.SecureJsonData {
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
}
pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
pluginSetting.Updated = time.Now()
pluginSetting.Enabled = cmd.Enabled
pluginSetting.JsonData = cmd.JsonData
pluginSetting.Pinned = cmd.Pinned
_, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
return err
}
})
}

View File

@ -0,0 +1,81 @@
///<reference path="../../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from 'app/core/core_module';
var template = `
<div class="graph-legend-popover">
<a class="drop-popopver-close" ng-click="ctrl.close();" href="" ng-hide="ctrl.autoClose">
<i class="fa fa-times-circle"></i>
</a>
<div ng-show="ctrl.series" class="p-b-1">
<label>Y Axis:</label>
<button ng-click="ctrl.toggleAxis(yaxis);" class="btn btn-small"
ng-class="{'btn-success': ctrl.series.yaxis === 1,
'btn-inverse': ctrl.series.yaxis === 2}">
Left
</button>
<button ng-click="ctrl.toggleAxis(yaxis);"
class="btn btn-small"
ng-class="{'btn-success': ctrl.series.yaxis === 2,
'btn-inverse': ctrl.series.yaxis === 1}">
Right
</button>
</div>
<p class="m-b-0">
<i ng-repeat="color in ctrl.colors" class="pointer fa fa-circle"
ng-style="{color:color}"
ng-click="ctrl.colorSelected(color);">&nbsp;</i>
</p>
</div>
`;
export class ColorPickerCtrl {
colors: any;
autoClose: boolean;
series: any;
showAxisControls: boolean;
/** @ngInject */
constructor(private $scope, private $rootScope) {
this.colors = $rootScope.colors;
this.autoClose = $scope.autoClose;
this.series = $scope.series;
}
toggleAxis(yaxis) {
this.$scope.toggleAxis();
if (!this.$scope.autoClose) {
this.$scope.dismiss();
}
}
colorSelected(color) {
this.$scope.colorSelected(color);
if (!this.$scope.autoClose) {
this.$scope.dismiss();
}
}
close() {
this.$scope.dismiss();
}
}
export function colorPicker() {
return {
restrict: 'E',
controller: ColorPickerCtrl,
bindToController: true,
controllerAs: 'ctrl',
template: template,
};
}
coreModule.directive('gfColorPicker', colorPicker);

View File

@ -77,8 +77,8 @@ export class GrafanaCtrl {
}); });
}; };
$rootScope.performance.scopeCount = scopes;
f(root); f(root);
$rootScope.performance.scopeCount = scopes;
return count; return count;
}; };

View File

@ -0,0 +1,55 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import $ from 'jquery';
import coreModule from '../../core_module';
import Drop from 'tether-drop';
export function popoverDirective() {
return {
restrict: 'E',
transclude: true,
link: function(scope, elem, attrs, ctrl, transclude) {
var inputElem = elem.prev();
if (inputElem.length === 0) {
console.log('Failed to find input element for popover');
return;
}
var offset = attrs.offset || '0 -10px';
transclude(function(clone, newScope) {
var content = document.createElement("div");
_.each(clone, (node) => {
content.appendChild(node);
});
var drop = new Drop({
target: inputElem[0],
content: content,
position: 'right middle',
classes: 'drop-help',
openOn: 'click',
tetherOptions: {
offset: offset
}
});
// inputElem.on('focus.popover', function() {
// drop.open();
// });
//
// inputElem.on('blur.popover', function() {
// close();
// });
scope.$on('$destroy', function() {
drop.destroy();
});
});
}
};
}
coreModule.directive('gfPopover', popoverDirective);

View File

@ -1,53 +1,51 @@
<ul class="sidemenu"> <ul class="sidemenu">
<li class="sidemenu-org-section" ng-if="ctrl.isSignedIn" class="dropdown"> <li class="sidemenu-org-section" ng-if="::ctrl.isSignedIn" class="dropdown">
<div class="sidemenu-org"> <a class="sidemenu-org" href="profile">
<div class="sidemenu-org-avatar"> <div class="sidemenu-org-avatar">
<img ng-if="ctrl.user.gravatarUrl" ng-src="{{ctrl.user.gravatarUrl}}"> <img ng-src="{{::ctrl.user.gravatarUrl}}">
<span class="sidemenu-org-avatar--missing"> <span class="sidemenu-org-avatar--missing">
<i class="fa fa-fw fa-user"></i> <i class="fa fa-fw fa-user"></i>
</span> </span>
</div> </div>
<div class="sidemenu-org-details"> <div class="sidemenu-org-details">
<span class="sidemenu-org-user sidemenu-item-text">{{ctrl.user.name}}</span> <span class="sidemenu-org-user sidemenu-item-text">{{::ctrl.user.name}}</span>
<span class="sidemenu-org-name sidemenu-item-text">{{ctrl.user.orgName}}</span> <span class="sidemenu-org-name sidemenu-item-text">{{::ctrl.user.orgName}}</span>
</div> </div>
</div> </a>
<i class="fa fa-caret-right"></i> <i class="fa fa-caret-right"></i>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li ng-repeat="menuItem in ctrl.orgMenu" ng-class="menuItem.cssClass"> <li ng-repeat="menuItem in ctrl.orgMenu" ng-class="::menuItem.cssClass">
<span ng-if="menuItem.section">{{menuItem.section}}</span> <span ng-show="::menuItem.section">{{::menuItem.section}}</span>
<a href="{{menuItem.url}}" ng-if="menuItem.url" target="{{menuItem.target}}"> <a href="{{::menuItem.url}}" ng-show="::menuItem.url" target="{{::menuItem.target}}">
<i class="{{menuItem.icon}}" ng-if="menuItem.icon"></i> <i class="{{::menuItem.icon}}" ng-show="::menuItem.icon"></i>
{{menuItem.text}} {{::menuItem.text}}
</a> </a>
<a ng-click="menuItem.click()" ng-if="menuItem.click"> <a ng-click="menuItem.click()" ng-show="::menuItem.click">
<i class="{{menuItem.icon}}"></i> <i class="{{::menuItem.icon}}"></i>
{{menuItem.text}} {{::menuItem.text}}
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
<li ng-repeat="item in ctrl.mainLinks" class="dropdown"> <li ng-repeat="item in ::ctrl.mainLinks" class="dropdown">
<a href="{{item.url}}" 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"> <span class="icon-circle sidemenu-icon">
<i class="{{item.icon}}" ng-show="item.icon"></i> <i class="{{::item.icon}}" ng-show="::item.icon"></i>
<img ng-src="{{item.img}}" ng-show="item.img"> <img ng-src="{{::item.img}}" ng-show="::item.img">
</span> </span>
<span class="sidemenu-item-text">{{item.text}}</span> <span class="sidemenu-item-text">{{::item.text}}</span>
<span class="fa fa-caret-right" ng-if="item.children"></span> <span class="fa fa-caret-right" ng-if="::item.children"></span>
</a> </a>
<ul class="dropdown-menu" role="menu" ng-if="item.children"> <ul class="dropdown-menu" role="menu" ng-if="::item.children">
<li ng-repeat="child in item.children"> <li ng-repeat="child in ::item.children" ng-class="{divider: child.divider}">
<a href="{{child.url}}"> <a href="{{::child.url}}">{{::child.text}}</a>
{{child.text}}
</a>
</li> </li>
</ul> </ul>
</li> </li>
<li ng-if="!ctrl.isSignedIn"> <li ng-show="::!ctrl.isSignedIn">
<a href="login" class="sidemenu-item" target="_self"> <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="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-in"></i></span>
<span class="sidemenu-item-text">Sign in</span> <span class="sidemenu-item-text">Sign in</span>

View File

@ -38,7 +38,6 @@ export class SideMenuCtrl {
openUserDropdown() { openUserDropdown() {
this.orgMenu = [ this.orgMenu = [
{section: 'You', cssClass: 'dropdown-menu-title'}, {section: 'You', cssClass: 'dropdown-menu-title'},
{text: 'Preferences', url: this.getUrl('/profile')},
{text: 'Profile', url: this.getUrl('/profile')}, {text: 'Profile', url: this.getUrl('/profile')},
]; ];
@ -100,6 +99,22 @@ export function sideMenuDirective() {
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: {}, scope: {},
link: function(scope, elem) {
// hack to hide dropdown menu
elem.on('click.dropdown', '.dropdown-menu a', function(evt) {
var menu = $(evt.target).parents('.dropdown-menu');
var parent = menu.parent();
menu.detach();
setTimeout(function() {
parent.append(menu);
}, 100);
});
scope.$on("$destory", function() {
elem.off('click.dropdown');
});
}
}; };
} }

View File

@ -24,6 +24,8 @@ import './partials';
import {grafanaAppDirective} from './components/grafana_app'; import {grafanaAppDirective} from './components/grafana_app';
import {sideMenuDirective} from './components/sidemenu/sidemenu'; import {sideMenuDirective} from './components/sidemenu/sidemenu';
import {searchDirective} from './components/search/search'; import {searchDirective} from './components/search/search';
import {popoverDirective} from './components/popover/popover';
import {colorPicker} from './components/colorpicker/colorpicker';
import {navbarDirective} from './components/navbar/navbar'; import {navbarDirective} from './components/navbar/navbar';
import {arrayJoin} from './directives/array_join'; import {arrayJoin} from './directives/array_join';
import 'app/core/controllers/all'; import 'app/core/controllers/all';
@ -32,4 +34,13 @@ import 'app/core/routes/routes';
import './filters/filters'; import './filters/filters';
import coreModule from './core_module'; import coreModule from './core_module';
export {arrayJoin, coreModule, grafanaAppDirective, sideMenuDirective, navbarDirective, searchDirective}; export {
arrayJoin,
coreModule,
grafanaAppDirective,
sideMenuDirective,
navbarDirective,
searchDirective,
colorPicker,
popoverDirective
};

View File

@ -25,7 +25,8 @@ function ($, _, coreModule) {
var dashboard = dashboardSrv.getCurrent(); var dashboard = dashboardSrv.getCurrent();
var time = '<i>' + dashboard.formatDate(event.min) + '</i>'; var time = '<i>' + dashboard.formatDate(event.min) + '</i>';
var tooltip = '<div class="graph-tooltip small"><div class="graph-tooltip-time">' + title + ' ' + time + '</div> ' ; var tooltip = '<div class="graph-annotation">';
tooltip += '<div class="graph-annotation-title">' + title + "</div>";
if (event.text) { if (event.text) {
var text = sanitizeString(event.text); var text = sanitizeString(event.text);
@ -42,9 +43,10 @@ function ($, _, coreModule) {
if (tags && tags.length) { if (tags && tags.length) {
scope.tags = tags; scope.tags = tags;
tooltip += '<span class="label label-tag" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>'; tooltip += '<span class="label label-tag small" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>';
} }
tooltip += '<div class="graph-annotation-time">' + time + '</div>' ;
tooltip += "</div>"; tooltip += "</div>";
var $tooltip = $(tooltip); var $tooltip = $(tooltip);

View File

@ -73,12 +73,7 @@ function ($, coreModule) {
}; };
var src = "'" + payload.src + "'"; var src = "'" + payload.src + "'";
var cssClass = payload.cssClass || 'gf-box'; var view = $('<div class="tabbed-view" ng-include="' + src + '"></div>');
var view = $('<div class="' + cssClass + '" ng-include="' + src + '"></div>');
if (payload.cssClass) {
view.addClass(payload.cssClass);
}
elem.append(view); elem.append(view);
$compile(elem.contents())(editorScope); $compile(elem.contents())(editorScope);

View File

@ -39,7 +39,7 @@ function (angular, coreModule, kbn) {
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : ''; var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : ''; var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
var template = '<div class="editor-option text-center"' + showIf + '>' + var template = '<div class="editor-option gf-form-checkbox text-center"' + showIf + '>' +
' <label for="' + attrs.model + '" class="small">' + ' <label for="' + attrs.model + '" class="small">' +
attrs.text + tip + '</label>' + attrs.text + tip + '</label>' +
'<input class="cr1" id="' + attrs.model + '" type="checkbox" ' + '<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +

View File

@ -160,13 +160,13 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
} }
// AppConfigCtrl // AppConfigCtrl
case 'app-config-ctrl': { case 'app-config-ctrl': {
let appModel = scope.ctrl.appModel; let model = scope.ctrl.model;
return System.import(appModel.module).then(function(appModule) { return System.import(model.module).then(function(appModule) {
return { return {
baseUrl: appModel.baseUrl, baseUrl: model.baseUrl,
name: 'app-config-' + appModel.appId, name: 'app-config-' + model.pluginId,
bindings: {appModel: "=", appEditCtrl: "="}, bindings: {appModel: "=", appEditCtrl: "="},
attrs: {"app-model": "ctrl.appModel", "app-edit-ctrl": "ctrl"}, attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
Component: appModule.ConfigCtrl, Component: appModule.ConfigCtrl,
}; };
}); });

View File

@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true); $locationProvider.html5Mode(true);
var loadOrgBundle = new BundleLoader('app/features/org/all'); var loadOrgBundle = new BundleLoader('app/features/org/all');
var loadAppsBundle = new BundleLoader('app/features/apps/all'); var loadPluginsBundle = new BundleLoader('app/features/plugins/all');
var loadAdminBundle = new BundleLoader('app/features/admin/admin'); var loadAdminBundle = new BundleLoader('app/features/admin/admin');
$routeProvider $routeProvider
@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'SnapshotsCtrl', controller : 'SnapshotsCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/apps', { .when('/plugins', {
templateUrl: 'public/app/features/apps/partials/list.html', templateUrl: 'public/app/features/plugins/partials/list.html',
controller: 'AppListCtrl', controller: 'PluginListCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/apps/:appId/edit', { .when('/plugins/:pluginId/edit', {
templateUrl: 'public/app/features/apps/partials/edit.html', templateUrl: 'public/app/features/plugins/partials/edit.html',
controller: 'AppEditCtrl', controller: 'PluginEditCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/apps/:appId/page/:slug', { .when('/plugins/:pluginId/page/:slug', {
templateUrl: 'public/app/features/apps/partials/page.html', templateUrl: 'public/app/features/plugins/partials/page.html',
controller: 'AppPageCtrl', controller: 'AppPageCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/global-alerts', { .when('/global-alerts', {
templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html', templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',

View File

@ -1,60 +0,0 @@
define([
'angular',
'lodash',
'jquery',
'../core_module',
],
function (angular, _, $, coreModule) {
'use strict';
coreModule.default.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) {
this.getTemplate = function(url) {
return $q.when($templateCache.get(url) || $http.get(url, {cache: true}));
};
this.show = function(options) {
var popover;
// hide other popovers
$('.popover').each(function() {
popover = $(this).prev().data('popover');
if (popover) {
popover.scope.$destroy();
popover.destroy();
}
});
options.scope.dismiss = function() {
popover = options.element.data('popover');
if (popover) {
popover.destroy();
}
options.scope.$destroy();
};
this.getTemplate(options.templateUrl).then(function(result) {
$timeout(function() {
var template = _.isString(result) ? result : result.data;
options.element.popover({
content: template,
placement: options.placement || 'bottom',
html: true
});
popover = options.element.data('popover');
popover.hasContent = function () {
return template;
};
popover.toggle();
popover.scope = options.scope;
$compile(popover.$tip)(popover.scope);
}, 1);
});
};
});
});

View File

@ -0,0 +1,55 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from 'app/core/core_module';
import Drop from 'tether-drop';
/** @ngInject **/
function popoverSrv($compile, $rootScope) {
this.show = function(options) {
var popoverScope = _.extend($rootScope.$new(true), options.model);
var drop;
function destroyDrop() {
setTimeout(function() {
if (drop.tether) {
drop.destroy();
}
});
}
popoverScope.dismiss = function() {
popoverScope.$destroy();
destroyDrop();
};
var contentElement = document.createElement('div');
contentElement.innerHTML = options.template;
$compile(contentElement)(popoverScope);
drop = new Drop({
target: options.element,
content: contentElement,
position: options.position,
classes: 'drop-popover',
openOn: options.openOn || 'hover',
hoverCloseDelay: 200,
tetherOptions: {
constraints: [{to: 'window', pin: true, attachment: "both"}]
}
});
drop.on('close', () => {
popoverScope.dismiss({fromDropClose: true});
destroyDrop();
});
drop.open();
};
}
coreModule.service('popoverSrv', popoverSrv);

View File

@ -12,39 +12,66 @@ function($, _) {
kbn.round_interval = function(interval) { kbn.round_interval = function(interval) {
switch (true) { switch (true) {
// 0.5s // 0.3s
case (interval <= 500): case (interval <= 300):
return 100; // 0.1s return 100; // 0.1s
// 5s // 0.75s
case (interval <= 5000): case (interval <= 750):
return 500; // 0.5s
// 1.5s
case (interval <= 1500):
return 1000; // 1s return 1000; // 1s
// 3.5s
case (interval <= 3500):
return 2000; // 2s
// 7.5s // 7.5s
case (interval <= 7500): case (interval <= 7500):
return 5000; // 5s return 5000; // 5s
// 15s // 12.5s
case (interval <= 15000): case (interval <= 12500):
return 10000; // 10s return 10000; // 10s
// 17.5s
case (interval <= 17500):
return 15000; // 15s
// 25s
case (interval <= 25000):
return 20000; // 20s
// 45s // 45s
case (interval <= 45000): case (interval <= 45000):
return 30000; // 30s return 30000; // 30s
// 3m // 1.5m
case (interval <= 180000): case (interval <= 90000):
return 60000; // 1m return 60000; // 1m
// 9m // 3.5m
case (interval <= 210000):
return 120000; // 2m
// 7.5m
case (interval <= 450000): case (interval <= 450000):
return 300000; // 5m return 300000; // 5m
// 20m // 12.5m
case (interval <= 1200000): case (interval <= 750000):
return 600000; // 10m return 600000; // 10m
// 12.5m
case (interval <= 1050000):
return 900000; // 15m
// 25m
case (interval <= 1500000):
return 1200000; // 20m
// 45m // 45m
case (interval <= 2700000): case (interval <= 2700000):
return 1800000; // 30m return 1800000; // 30m
// 2h // 1.5h
case (interval <= 7200000): case (interval <= 5400000):
return 3600000; // 1h return 3600000; // 1h
// 6h // 2.5h
case (interval <= 21600000): case (interval <= 9000000):
return 7200000; // 2h
// 4.5h
case (interval <= 16200000):
return 10800000; // 3h return 10800000; // 3h
// 9h
case (interval <= 32400000):
return 21600000; // 6h
// 24h // 24h
case (interval <= 86400000): case (interval <= 86400000):
return 43200000; // 12h return 43200000; // 12h
@ -593,12 +620,12 @@ function($, _) {
{text: 'packets/sec', value: 'pps'}, {text: 'packets/sec', value: 'pps'},
{text: 'bits/sec', value: 'bps'}, {text: 'bits/sec', value: 'bps'},
{text: 'bytes/sec', value: 'Bps'}, {text: 'bytes/sec', value: 'Bps'},
{text: 'kilobites/sec', value: 'Kbits'}, {text: 'kilobits/sec', value: 'Kbits'},
{text: 'kilobytes/sec', value: 'KBs'}, {text: 'kilobytes/sec', value: 'KBs'},
{text: 'megabites/sec', value: 'Mbits'}, {text: 'megabits/sec', value: 'Mbits'},
{text: 'megabytes/sec', value: 'MBs'}, {text: 'megabytes/sec', value: 'MBs'},
{text: 'gigabytes/sec', value: 'GBs'}, {text: 'gigabytes/sec', value: 'GBs'},
{text: 'gigabites/sec', value: 'Gbits'}, {text: 'gigabits/sec', value: 'Gbits'},
] ]
}, },
{ {

View File

@ -1,4 +1,4 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -1,58 +1,47 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button> <a href="admin/orgs" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Orgs
</a>
</navbar> </navbar>
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Edit Organization</h1>
Edit Organization
</h1>
</div> </div>
<form name="orgDetailsForm"> <form name="orgDetailsForm" class="gf-form-group">
<div> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label width-10">Name</span>
<ul class="tight-form-list"> <input type="text" required ng-model="org.name" class="gf-form-input max-width-14" >
<li class="tight-form-item" style="width: 100px"> </div>
Name
</li>
<li>
<input type="text" required ng-model="org.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<br> <div class="gf-form-button-row">
<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button> <button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
</form> </div>
</form>
<h3> <h3 class="page-heading">Organization Users</h3>
Organization Users
</h3>
<table class="grafana-options-table form-inline"> <table class="grafana-options-table">
<tr> <tr>
<th>Username</th> <th>Username</th>
<th>Email</th> <th>Email</th>
<th>Role</th> <th>Role</th>
<th></th> <th></th>
</tr> </tr>
<tr ng-repeat="orgUser in orgUsers"> <tr ng-repeat="orgUser in orgUsers">
<td>{{orgUser.login}}</td> <td>{{orgUser.login}}</td>
<td>{{orgUser.email}}</td> <td>{{orgUser.email}}</td>
<td> <td>
<select type="text" ng-model="orgUser.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(orgUser)"> <select type="text" ng-model="orgUser.role" class="gf-form-input max-width-8" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
</select> </select>
</td> </td>
<td style="width: 1%"> <td style="width: 1%">
<a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-mini"> <a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div>
</div> </div>

View File

@ -1,129 +1,78 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button> <a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar> </navbar>
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Edit User</h1>
Edit User
</h1>
</div> </div>
<form name="userForm"> <form name="userForm" class="gf-form-group">
<div> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label width-10">Name</span>
<ul class="tight-form-list"> <input type="text" required ng-model="user.name" class="gf-form-input max-width-25" >
<li class="tight-form-item" style="width: 100px">
Name
</li>
<li>
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Email</span>
<li class="tight-form-item" style="width: 100px"> <input type="email" ng-model="user.email" class="gf-form-input max-width-25" >
Email
</li>
<li>
<input type="email" ng-model="user.email" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Username</span>
<li class="tight-form-item" style="width: 100px"> <input type="text" ng-model="user.login" class="gf-form-input max-width-25" >
Username
</li>
<li>
<input type="text" ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div>
<br> <div class="gf-form-button-row">
<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button> <button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
</div>
</form> </form>
<h3> <h3 class="page-heading">Change password</h3>
Change password
</h3>
<form name="passwordForm"> <form name="passwordForm" class="gf-form-group">
<div> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label width-10">New password</span>
<ul class="tight-form-list"> <input type="password" required ng-minlength="4" ng-model="password" class="gf-form-input max-width-25">
<li class="tight-form-item" style="width: 100px">
New password
</li>
<li>
<input type="password" required ng-minlength="4" ng-model="password" class="input-xxlarge tight-form-input last">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
<br> <div class="gf-form-button-row">
<button type="submit" class="pull-right btn btn-success" ng-click="setPassword()">Update</button> <button type="submit" class="btn btn-success" ng-click="setPassword()">Update</button>
</form>
<h3>
Permissions
</h3>
<div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item last">
Grafana Admin&nbsp;
<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
<label for="permissions.isGrafanaAdmin" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="updatePermissions()">Update</button>
<br>
</div>
<h3>
Organizations
</h3>
<form name="addOrgForm">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
Add organization
</li>
<li>
<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs"
required class="input-xlarge tight-form-input" placeholder="organization name">
</li>
<li class="tight-form-item">
Role
</li>
<li>
<select type="text" ng-model="newOrg.role" class="input-small tight-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']">
</select>
</li>
<li>
<button class="btn btn-success tight-form-btn" ng-click="addOrgUser()">Add</button>
</li>
<div class="clearfix"></div>
</ul>
</div> </div>
</form> </form>
<table class="grafana-options-table form-inline"> <h3 class="page-heading">Permissions</h3>
<form name="passwordForm" class="gf-form-group">
<div class="gf-form" >
<editor-checkbox text="Grafana Admin" model="permissions.isGrafanaAdmin" style="line-height: 1.5rem;"></editor-checkbox>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="updatePermissions()">Update</button>
</div>
</form>
<h3 class="page-heading">Organizations</h3>
<form name="addOrgForm" class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-12">Add organization</span>
<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs" required class="gf-form-input max-width-20" placeholder="organization name">
</div>
<div class="gf-form">
<span class="gf-form-label">Role</span>
<select type="text" ng-model="newOrg.role" class="gf-form-input width-10" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"></select>
</div>
<div class="gf-form">
<button class="btn btn-success gf-form-btn" ng-click="addOrgUser()">Add</button>
</div>
</div>
</form>
<table class="grafana-options-table">
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Role</th> <th>Role</th>
@ -134,7 +83,7 @@
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span> {{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
</td> </td>
<td> <td>
<select type="text" ng-model="org.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(org)"> <select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(org)">
</select> </select>
</td> </td>
<td style="width: 1%"> <td style="width: 1%">

View File

@ -1,63 +1,35 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button> <a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar> </navbar>
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Add new user</h1>
Add new user
</h1>
</div> </div>
<form name="userForm"> <form name="userForm" class="gf-form-group">
<div> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label width-10">Name</span>
<ul class="tight-form-list"> <input type="text" required ng-model="user.name" class="gf-form-input max-width-20" >
<li class="tight-form-item" style="width: 100px"> </div>
<strong>Name</strong> <div class="gf-form">
</li> <span class="gf-form-label width-10">Email</span>
<li> <input type="email" ng-model="user.email" class="gf-form-input max-width-20" >
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" > </div>
</li> <div class="gf-form">
</ul> <span class="gf-form-label width-10">Username</span>
<div class="clearfix"></div> <input type="text" ng-model="user.login" class="gf-form-input max-width-20" >
</div> </div>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Password</span>
<li class="tight-form-item" style="width: 100px"> <input type="password" required ng-model="user.password" class="gf-form-input max-width-20" >
<strong>Email</strong>
</li>
<li>
<input type="email" ng-model="user.email" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
<strong>Username</strong>
</li>
<li>
<input type="text" ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
<strong>Password</strong>
</li>
<li>
<input type="password" required ng-model="user.password" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
<br> <div class="gf-form-button-row">
<button type="submit" class="pull-right btn btn-success" ng-click="create()">Create</button> <button type="submit" class="btn btn-success" ng-click="create()">Create</button>
</div>
</form> </form>
</div> </div>

View File

@ -1,12 +1,13 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button> <a href="admin/orgs" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Orgs
</a>
</navbar> </navbar>
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Organizations</h1>
Organizations
</h1>
</div> </div>
<table class="filter-table form-inline"> <table class="filter-table form-inline">

View File

@ -3,9 +3,7 @@
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Server settings</h1>
Server settings
</h1>
</div> </div>
<div class="grafana-info-box span8" style="margin: 20px 0 25px 0"> <div class="grafana-info-box span8" style="margin: 20px 0 25px 0">

View File

@ -3,9 +3,7 @@
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<h1> <h1>Stats</h1>
Stats
</h1>
</div> </div>
<table class="filter-table form-inline"> <table class="filter-table form-inline">

View File

@ -1,5 +1,8 @@
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin"> <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button> <a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -1,106 +1,99 @@
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()"> <div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
<div class="tabbed-view-header">
<div class="gf-box-header"> <h2 class="tabbed-view-title">
<div class="gf-box-title">
<i class="fa fa-bolt"></i>
Annotations Annotations
</div> </h2>
<div class="tabs"> <ul class="gf-tabs">
<ul class="nav nav-tabs"> <li class="gf-tabs-item" >
<li ng-class="{active: mode === 'list'}"> <a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
<a ng-click="mode = 'list';"> List
List </a>
</a> </li>
</li> <li class="gf-tabs-item" ng-show="mode === 'edit'">
<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
{{currentAnnotation.name}}
</a>
</li>
<li class="gf-tabs-item" ng-show="mode === 'new'">
<span class="active gf-tabs-link">New</span>
</li>
</ul>
<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'"> <button class="tabbed-view-close-btn" ng-click="dismiss();">
<a>
{{currentAnnotation.name}}
</a>
</li>
<li ng-class="{active: mode === 'new'}">
<a ng-click="mode = 'new';">
<i class="fa fa-plus"></i>
New
</a>
</li>
</ul>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body">
<div class="tabbed-view-body">
<div class="editor-row row" ng-if="mode === 'list'"> <div class="editor-row row" ng-if="mode === 'list'">
<div class="span6"> <div ng-if="annotations.length === 0">
<div ng-if="annotations.length === 0"> <em>No annotations defined</em>
<em>No annotations defined</em> </div>
</div> <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> &nbsp;
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp; {{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> <td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%" class="nobg"> <td style="width: 1%">
<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini"> <a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
Edit Edit
</a> </a>
</td> </td>
<td style="width: 1%" class="nobg"> <td style="width: 1%">
<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini"> <a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div>
<div class="gf-form" ng-show="mode === 'list'">
<div class="gf-form-button-row">
<a type="button" class="btn gf-form-button btn-success" ng-click="mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
</div> </div>
</div> </div>
<div ng-if="mode === 'edit' || mode === 'new'"> <div class="annotations-basic-settings" ng-if="mode === 'edit' || mode === 'new'">
<div class="editor-row"> <div class="gf-form-group">
<div class="editor-option"> <div class="gf-form-inline">
<label class="small">Name</label> <div class="gf-form gf-size-max-xxl">
<input type="text" class="input-medium" ng-model='currentAnnotation.name' placeholder="name"></input> <span class="gf-form-label">Name</span>
</div> <input type="text" class="gf-form-input" ng-model='currentAnnotation.name' placeholder="name"></input>
<div class="editor-option"> </div>
<label class="small">Datasource</label> <div class="gf-form">
<select ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select> <span class="gf-form-label max-width-10">Datasource</span>
</div> <div class="gf-form-select-wrapper">
<div class="editor-option text-center"> <select class="gf-form-input gf-size-auto" ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
<label class="small">Icon color</label> </div>
<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker> </div>
</div> <div class="gf-form">
<div class="editor-option"> <label class="gf-form-label">
<label class="small">Icon size</label> <span>Color</span>
<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select> <spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
</div> </label>
<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool> </div>
<div class="editor-option text-center">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
</div> </div>
</div> </div>
<rebuild-on-change property="currentAnnotation.datasource"> <rebuild-on-change property="currentDatasource">
<plugin-component type="annotations-query-ctrl"> <plugin-component type="annotations-query-ctrl">
</plugin-component> </plugin-component>
</rebuild-on-change> </rebuild-on-change>
<br> <div class="gf-form">
<button ng-show="mode === 'new'" type="button" class="btn btn-success" ng-click="add()">Add</button> <div class="gf-form-button-row p-y-0">
<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button> <button ng-show="mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="add()">Add</button>
<br> <button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update()">Update</button>
<br> </div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,49 +0,0 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
export class AppEditCtrl {
appModel: any;
appId: any;
includedPanels: any;
includedDatasources: any;
/** @ngInject */
constructor(private backendSrv: any, private $routeParams: any) {
this.appModel = {};
this.appId = $routeParams.appId;
this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(result => {
this.appModel = result;
this.includedPanels = _.where(result.includes, {type: 'panel'});
this.includedDatasources = _.where(result.includes, {type: 'datasource'});
});
}
update() {
var updateCmd = _.extend({
appId: this.appModel.appId,
orgId: this.appModel.orgId,
enabled: this.appModel.enabled,
pinned: this.appModel.pinned,
jsonData: this.appModel.jsonData,
secureJsonData: this.appModel.secureJsonData,
}, {});
this.backendSrv.post(`/api/org/apps/${this.appId}/settings`, updateCmd).then(function() {
window.location.href = window.location.href;
});
}
toggleEnabled() {
this.update();
}
togglePinned() {
this.update();
}
}
angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl);

View File

@ -1,17 +0,0 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
export class AppListCtrl {
apps: any[];
/** @ngInject */
constructor(private backendSrv: any) {
this.backendSrv.get('api/org/apps').then(apps => {
this.apps = apps;
});
}
}
angular.module('grafana.controllers').controller('AppListCtrl', AppListCtrl);

View File

@ -1,108 +0,0 @@
<navbar title="Plugins" title-url="Plugins" icon="icon-gf icon-gf-apps">
</navbar>
<div class="page-container">
<div class="flex-container">
<div class="flex-column app-edit-logo-box">
<img src="{{ctrl.appModel.info.logos.large}}">
</div>
<div class="flex-column">
<h1>
{{ctrl.appModel.name}}
</h1>
<div class="app-edit-description">
{{ctrl.appModel.info.description}}<br>
<span style="small">
Version: {{ctrl.appModel.info.version}} &nbsp; &nbsp; Updated: {{ctrl.appModel.info.updated}}
</span>
</div>
<div class="form-inline">
<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
&nbsp; &nbsp; &nbsp;
<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="ctrl.togglePinned()"></editor-checkbox>
</div>
</div>
<div class="flex-column">
<ul class="app-edit-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>
<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 ng-show="!ctrl.includedPanels.length"><em class="small">None</em></li>
<li ng-repeat="panel in ctrl.includedPanels">
{{panel.name}}
</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 ng-show="!ctrl.includedDatasources.length"><em class="small">None</em></li>
<li ng-repeat="ds in ctrl.includedDatasources">
{{ds.name}}
</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="apps/{{ctrl.appId}}/page/{{page.slug}}" 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 ng-if="ctrl.appModel.appId">
<plugin-component type="app-config-ctrl"></plugin-component>
<div class="clearfix"></div>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</div>
</section>
</div>

View File

@ -1,47 +0,0 @@
<navbar title="Plugins" icon="icon-gf icon-gf-apps">
</navbar>
<div class="page-container">
<div class="page-header">
<h1>Plugins</h1>
</div>
<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/{{app.appId}}/edit">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
<span class="filter-list-card-title">
<a href="apps/{{app.appId}}/edit">
{{app.name}}
</a>
&nbsp; &nbsp;
<span class="label label-info" ng-if="app.enabled">
Enabled
</span>
&nbsp;
<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>

View File

@ -23,12 +23,12 @@
<li ng-show="dashboardMeta.canShare" class="dropdown"> <li ng-show="dashboardMeta.canShare" class="dropdown">
<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a> <a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li ng-if="dashboardMeta.canEdit"> <li>
<a class="pointer" ng-click="shareDashboard(0)"> <a class="pointer" ng-click="shareDashboard(0)">
<i class="fa fa-link"></i> Link to Dashboard <i class="fa fa-link"></i> Link to Dashboard
</a> </a>
</li> </li>
<li ng-if="dashboardMeta.canEdit"> <li>
<a class="pointer" ng-click="shareDashboard(1)"> <a class="pointer" ng-click="shareDashboard(1)">
<i class="icon-gf icon-gf-snapshot"></i>Snapshot sharing <i class="icon-gf icon-gf-snapshot"></i>Snapshot sharing
</a> </a>

View File

@ -12,6 +12,8 @@ export class DashNavCtrl {
$scope.init = function() { $scope.init = function() {
$scope.onAppEvent('save-dashboard', $scope.saveDashboard); $scope.onAppEvent('save-dashboard', $scope.saveDashboard);
$scope.onAppEvent('delete-dashboard', $scope.deleteDashboard); $scope.onAppEvent('delete-dashboard', $scope.deleteDashboard);
$scope.onAppEvent('export-dashboard', $scope.snapshot);
$scope.onAppEvent('quick-snapshot', $scope.quickSnapshot);
$scope.showSettingsMenu = $scope.dashboardMeta.canEdit || $scope.contextSrv.isEditor; $scope.showSettingsMenu = $scope.dashboardMeta.canEdit || $scope.contextSrv.isEditor;
@ -52,6 +54,10 @@ export class DashNavCtrl {
}); });
}; };
$scope.quickSnapshot = function() {
$scope.shareDashboard(1);
};
$scope.openSearch = function() { $scope.openSearch = function() {
$scope.appEvent('show-dash-search'); $scope.appEvent('show-dash-search');
}; };

View File

@ -60,6 +60,14 @@ function(angular, $) {
scope.appEvent('zoom-out', evt); scope.appEvent('zoom-out', evt);
}, { inputDisabled: true }); }, { inputDisabled: true });
keyboardManager.bind('ctrl+e', function(evt) {
scope.appEvent('export-dashboard', evt);
}, { inputDisabled: true });
keyboardManager.bind('ctrl+i', function(evt) {
scope.appEvent('quick-snapshot', evt);
}, { inputDisabled: true });
keyboardManager.bind('esc', function() { keyboardManager.bind('esc', function() {
var popups = $('.popover.in'); var popups = $('.popover.in');
if (popups.length > 0) { if (popups.length > 0) {

View File

@ -1,22 +1,23 @@
<div ng-controller="GraphiteImportCtrl" ng-init="init()"> <div ng-controller="GraphiteImportCtrl" ng-init="init()">
<div ng-if="datasources.length > 0"> <div ng-if="datasources.length > 0">
<h2 style="margin-top: 30px;">Load dashboard from Graphite-Web</h2> <h2 class="page-heading">Load dashboard from Graphite-Web</h2>
<div class="tight-form last"> <div class="gf-form-group">
<ul class="tight-form-list"> <div class="gf-form-inline">
<li class="tight-form-item" style="width: 150px"> <div class="gf-form">
<strong>Data source</strong> <span class="gf-form-label width-10">Data source</span>
</li> </div>
<li> <div class="gf-form">
<select type="text" ng-model="options.sourceName" class="input-medium tight-form-input" ng-options="f for f in datasources"> <div class="gf-form-select-wrapper">
</select> <select type="text" ng-model="options.sourceName" class="gf-form-input gf-size-auto" ng-options="f for f in datasources">
</li> </select>
<li style="float: right"> </div>
<button class="btn btn-inverse tight-form-btn" ng-click="listAll()">List dashboards</button> </div>
</li> <div class="gf-form">
<div class="clearfix"></div> <button class="btn btn-success pull-right" ng-click="listAll()">List dashboards</button>
</ul> </div>
</div>
</div> </div>
<table class="grafana-options-table" style="margin-top: 20px;"> <table class="grafana-options-table" style="margin-top: 20px;">

View File

@ -24,7 +24,9 @@
<div class="gf-form"> <div class="gf-form">
<div class="gf-form-label">Dashboard source</div> <div class="gf-form-label">Dashboard source</div>
<div> <div>
<select type="text" ng-model="sourceName" class="input-medium tight-form-input" ng-options="f for f in datasources"></select> <div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="sourceName" ng-options="f for f in datasources"></select>
</div>
</div> </div>
<div class="gf-form-btn"> <div class="gf-form-btn">
<button class="btn btn-success" ng-click="startImport()">Import</button> <button class="btn btn-success" ng-click="startImport()">Import</button>

View File

@ -1,62 +0,0 @@
<div ng-controller="DashLinksController">
<div class="editor-row">
<div class="tight-form-section">
<h5>Links and Dash Navigation</h5>
<div ng-repeat="link in panel.links">
<div class="tight-form" >
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
</li>
<li class="tight-form-item" style="width: 80px;">Link title</li>
<li>
<input type="text" ng-model="link.title" class="input-medium tight-form-input">
</li>
<li class="tight-form-item">Type</li>
<li>
<select class="input-medium tight-form-input" style="width: 101px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
</li>
<li class="tight-form-item" ng-show="link.type === 'dashboard'">Dashboard</li>
<li ng-show="link.type === 'dashboard'">
<input type="text"
ng-model="link.dashboard"
bs-typeahead="searchDashboards"
class="input-large tight-form-input">
</li>
<li class="tight-form-item" ng-show="link.type === 'absolute'">Url</li>
<li ng-show="link.type === 'absolute'">
<input type="text" ng-model="link.url" class="input-xlarge tight-form-input">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item">
<i class="fa fa-remove invisible"></i>
</li>
<li class="tight-form-item" style="width: 80px;">
Params
<tip>Use var-variableName=value to pass templating variables.</tip>
</li>
<li>
<input type="text" ng-model="link.params" class="input-xxlarge tight-form-input">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="editor-row">
<br>
<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
</div>
</div>

View File

@ -1,106 +1,81 @@
<div class="gf-box-header"> <div class="tabbed-view-header">
<div class="gf-box-title"> <h2 class="tabbed-view-title">
<i class="fa fa-cogs"></i>
Settings Settings
</div> </h2>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;"> <ul class="gf-tabs">
<div ng-repeat="tab in ['General', 'Rows', 'Links', 'Time picker', 'Metadata']" data-title="{{tab}}"> <li class="gf-tabs-item" ng-repeat="tab in ::['General', 'Rows', 'Links', 'Time picker', 'Metadata']">
</div> <a class="gf-tabs-link" ng-click="editor.index = $index" ng-class="{active: editor.index === $index}">
</div> {{::tab}}
</a>
</li>
</ul>
<button class="gf-box-header-close-btn" ng-click="dismiss();"> <button class="tabbed-view-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body" style="padding-bottom: 50px;"> <div class="tabbed-view-body">
<div ng-if="editor.index == 0"> <div ng-if="editor.index == 0">
<div class="editor-row">
<div class="tight-form-section"> <h5 class="section-heading">Dashboard Detail</h5>
<h5>Dashboard info</h5> <div class="gf-form-group">
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <label class="gf-form-label width-7">Title</label>
<li class="tight-form-item" style="width: 90px"> <input type="text" class="gf-form-input max-width-25" ng-model='dashboard.title'></input>
Title </div>
</li> <div class="gf-form">
<li> <label class="gf-form-label width-7">Tags<tip>Press enter to a add tag</tip></label>
<input type="text" class="input-large tight-form-input" ng-model='dashboard.title'></input> <bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
</li> </bootstrap-tagsinput>
<li class="tight-form-item"> </div>
Tags
<tip>Press enter to a add tag</tip> <div class="gf-form">
</li> <label class="gf-form-label width-7">Timezone</label>
<li> <div class="gf-form-select-wrapper">
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags"> <select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f for f in ['browser','utc']"></select>
</bootstrap-tagsinput>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 90px">
Timezone
</li>
<li>
<select ng-model="dashboard.timezone" class='input-small tight-form-input' ng-options="f for f in ['browser','utc']"></select>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row"> <h5 class="section-heading">On/Off Toggles</h5>
<div class="tight-form-section"> <div class="gf-form-group">
<h5>Toggles</h5> <div class="gf-form-inline">
<div class="tight-form last"> <div class="gf-form">
<ul class="tight-form-list"> <editor-checkbox text="Editable" model="dashboard.editable"></editor-checkbox>
<li class="tight-form-item"> </div>
<editor-checkbox text="Editable" model="dashboard.editable"></editor-checkbox> <div class="gf-form">
</li> <editor-checkbox text="Hide Controls (CTRL+H)" model="dashboard.hideControls"></editor-checkbox>
<li class="tight-form-item"> </div>
<editor-checkbox text="Hide Controls (CTRL+H)" model="dashboard.hideControls"></editor-checkbox> <div class="gf-form">
</li> <editor-checkbox text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-checkbox>
<li class="tight-form-item last">
<editor-checkbox text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div ng-if="editor.index == 1"> <div ng-if="editor.index == 1">
<div class="editor-row"> <h5 class="section-heading">Rows settings</h5>
<div class="tight-form-section">
<h5>Rows settings</h5> <div class="gf-form-group">
<div class="tight-form-container"> <div class="gf-form-inline" ng-repeat="row in dashboard.rows">
<div class="tight-form" ng-repeat="row in dashboard.rows"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label">Title</span>
<li class="tight-form-item"> <input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
Title <editor-checkbox text="Show title" model="row.showTitle"></editor-checkbox>
</li> </div>
<li>
<input type="text" class="input tight-form-input" style="width: 400px;" ng-model='row.title'></input> <div class="gf-form">
</li> <button class="btn btn-inverse btn-mini" style="margin-right: 5px;" ng-click="dashboard.rows = _.without(dashboard.rows,row)">
<li class="tight-form-item"> <i class="fa fa-trash"></i>
<editor-checkbox text="Show title" model="row.showTitle"></editor-checkbox> </button>
</li> <button class="btn btn-inverse btn-mini" ng-hide="$first" style="margin-right: 5px;" ng-click="_.move(dashboard.rows,$index,$index-1)">
<li class="tight-form-item last"> <i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
<i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-class="{'invisible': $first}" class="pointer fa fa-arrow-up"></i> </button>
</li> <button class="btn btn-inverse btn-mini" ng-hide="$last" style="margin-right: 5px;" ng-click="_.move(dashboard.rows,$index,$index+1)">
<li class="tight-form-item last"> <i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
<i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-class="{'invisible': $last}" class="pointer fa fa-fw fa-arrow-down"></i> </button>
</li>
<li class="tight-form-item last">
<i ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="pointer fa fa-remove"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -114,69 +89,31 @@
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings> <gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
</div> </div>
<div ng-if="editor.index == 4"> <div ng-if="editor.index == 4">
<div class="row"> <h5 class="section-heading">Dashboard info</h5>
<h5>Dashboard info</h5> <div class="gf-form-group">
<div class="pull-left tight-form"> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label width-10">Last updated at:</span>
<ul class="tight-form-list"> <span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
<li class="tight-form-item" style="width: 120px"> </div>
Last updated at: <div class="gf-form">
</li> <span class="gf-form-label width-10">Last updated by:</span>
<li class="tight-form-item" style="width: 180px"> <span class="gf-form-label width-18">{{dashboardMeta.updatedBy}}&nbsp;</span>
{{formatDate(dashboardMeta.updated)}} </div>
</li> <div class="gf-form">
</ul> <span class="gf-form-label width-10">Created at:</span>
<div class="clearfix"></div> <span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}}&nbsp;</span>
</div> </div>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Created by:</span>
<li class="tight-form-item" style="width: 120px"> <span class="gf-form-label width-18">{{dashboardMeta.createdBy}}&nbsp;</span>
Last updated by: </div>
</li> <div class="gf-form">
<li class="tight-form-item" style="width: 180px"> <span class="gf-form-label width-10">Current version:</span>
{{dashboardMeta.updatedBy}} <span class="gf-form-label width-18">{{dashboardMeta.version}}&nbsp;</span>
</li> </div>
</ul> </div>
<div class="clearfix"></div> </div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 120px">
Created at:
</li>
<li class="tight-form-item" style="width: 180px">
{{formatDate(dashboardMeta.created)}}
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 120px">
Created by:
</li>
<li class="tight-form-item" style="width: 180px">
{{dashboardMeta.createdBy}}
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 120px">
Current version:
</li>
<li class="tight-form-item" style="width: 180px">
{{dashboardMeta.version}}
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>

View File

@ -35,53 +35,30 @@
<div ng-include src="'shareLinkOptions.html'"></div> <div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form"> <div class="gf-form-group position-center">
<div class="gf-form-row"> <div class="gf-form width-30" >
<span class="gf-fluid-input"> <textarea rows="5" data-share-panel-url class="gf-form-input width-30" ng-model='iframeHtml'></textarea>
<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea> </div>
</span> </div>
<div class="gf-form-group">
<div class="gf-form position-center">
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div> </div>
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div> </div>
</script> </script>
<script type="text/ng-template" id="shareLinkOptions.html"> <script type="text/ng-template" id="shareLinkOptions.html">
<div class="editor-row" style="margin: 11px 20px 33px 20px"> <div class="gf-form-group position-center">
<div class="section"> <div class="gf-form">
<div class="tight-form"> <editor-checkbox text="Current time range" model="options.forCurrent" change="updated()"></editor-checkbox>
<ul class="tight-form-list"> </div>
<li class="tight-form-item" style="width: 170px;"> <div class="gf-form">
<label class="checkbox-label" for="options.forCurrent">Current time range</label> <editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="updated()"></editor-checkbox>
</li> </div>
<li class="tight-form-item last"> <div class="gf-form">
<input class="cr1" id="options.forCurrent" type="checkbox" ng-model="options.forCurrent" ng-checked="options.forCurrent" ng-change="buildUrl()"> <span class="gf-form-label">Theme</span>
<label for="options.forCurrent" class="cr1"></label> <div class="gf-form-select-wrapper max-width-10">
</li> <select class="gf-form-input" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px">
<label class="checkbox-label" for="options.includeTemplateVars">Include template variables</label>
</li>
<li class="tight-form-item last">
<input class="cr1" id="options.includeTemplateVars" type="checkbox" ng-model="options.includeTemplateVars" ng-checked="options.includeTemplateVars" ng-change="buildUrl()">
<label for="options.includeTemplateVars" 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: 170px">
Theme
</li>
<li>
<select class="input-small tight-form-input last" style="width: 211px" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
@ -93,14 +70,19 @@
</div> </div>
<div ng-include src="'shareLinkOptions.html'"></div> <div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form"> <div class="gf-form-group position-center">
<div class="gf-form-row"> <div class="gf-form-inline">
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
<span class="gf-fluid-input"> <div class="gf-form width-30">
<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input> <input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
</span> </div>
<div class="gf-form pull-right">
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div>
</div> </div>
<div class="editor-row" style="margin-top: 5px;" ng-show="modeSharePanel"> </div>
<div class="gf-form-group">
<div class="gf-form position-center" ng-show="modeSharePanel">
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a> <a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
</div> </div>
</div> </div>
@ -132,29 +114,15 @@
</p> </p>
</div> </div>
<div class="editor-row share-modal-options" style=""> <div class="gf-form-group share-modal-options position-center">
<div class="section" ng-if="step === 1"> <div class="gf-form" ng-if="step === 1">
<div class="tight-form"> <span class="gf-form-label width-12">Snapshot name</span>
<ul class="tight-form-list"> <input type="text" ng-model="snapshot.name" class="gf-form-input max-width-15" >
<li class="tight-form-item" style="width: 110px;"> </div>
Snapshot name <div class="gf-form" ng-if="step === 1">
</li> <span class="gf-form-label width-12">Expire</span>
<li> <div class="gf-form-select-wrapper max-width-15">
<input type="text" ng-model="snapshot.name" class="input-large tight-form-input last" > <select class="gf-form-input" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px">
Expire
</li>
<li>
<select class="input-small tight-form-input last" style="width: 211px" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
@ -168,21 +136,21 @@
<button class="btn btn-inverse btn-large" data-clipboard-text="{{snapshotUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy Link</button> <button class="btn btn-inverse btn-large" data-clipboard-text="{{snapshotUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy Link</button>
</div> </div>
</div> </div>
</div>
<div ng-if="step === 1"> <div ng-if="step === 1" class="gf-form-buttons-row">
<button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading"> <button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading">
<i class="fa fa-save"></i> <i class="fa fa-save"></i>
Local Snapshot Local Snapshot
</button> </button>
<button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading"> <button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading">
<i class="fa fa-cloud-upload"></i> <i class="fa fa-cloud-upload"></i>
{{sharingButtonText}} {{sharingButtonText}}
</button> </button>
</div> </div>
<div class="pull-right" ng-if="step === 2" style="padding: 5px"> <div class="pull-right" ng-if="step === 2" style="padding: 5px">
Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a> Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a>
</div>
</div> </div>
</div> </div>

View File

@ -1,30 +1,27 @@
<div class="submenu-controls"> <div class="submenu-controls">
<div class="tight-form borderless"> <ul ng-if="ctrl.dashboard.templating.list.length > 0">
<li ng-repeat="variable in ctrl.variables" class="submenu-item">
<span class="submenu-item-label template-variable " ng-show="!variable.hideLabel">
{{variable.label || variable.name}}:
</span>
<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
</li>
</ul>
<ul class="tight-form-list" ng-if="ctrl.dashboard.templating.list.length > 0"> <ul ng-if="ctrl.dashboard.annotations.list.length > 0">
<li ng-repeat="variable in ctrl.variables" class="submenu-item"> <li ng-repeat="annotation in ctrl.dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
<span class="template-variable tight-form-item" ng-show="!variable.hideLabel" style="padding-right: 5px"> <a ng-click="ctrl.disableAnnotation(annotation)">
{{variable.label || variable.name}}: <i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
</span> {{annotation.name}}
<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown> <input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable">
</li> <label for="hideYAxis" class="cr1"></label>
</ul> </a>
</li>
</ul>
<ul class="tight-form-list" ng-if="ctrl.dashboard.annotations.list.length > 0"> <ul class="pull-right" ng-if="ctrl.dashboard.links.length > 0">
<li ng-repeat="annotation in ctrl.dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}"> <dash-links-container links="ctrl.dashboard.links"></dash-links-container>
<a ng-click="ctrl.disableAnnotation(annotation)"> </ul>
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
{{annotation.name}}
<input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable">
<label for="hideYAxis" class="cr1"></label>
</a>
</li>
</ul>
<ul class="tight-form-list pull-right" ng-if="ctrl.dashboard.links.length > 0"> <div class="clearfix"></div>
<dash-links-container links="ctrl.dashboard.links"></dash-links-container>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>

View File

@ -1,48 +1,15 @@
<div class="editor-row"> <div class="editor-row">
<div class="section"> <div class="gf-form-group">
<div> <div class="gf-form">
<!-- <div class="tight&#45;form"> --> <span class="gf-form-label width-10">Auto-refresh</span>
<!-- <ul class="tight&#45;form&#45;list"> --> <input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.refresh_intervals" array-join>
<!-- <li class="tight&#45;form&#45;item" style="width: 118px"> --> </div>
<!-- Relative times --> <div class="gf-form">
<!-- </li> --> <span class="gf-form-label width-10">Now delay now-</span>
<!-- <li> --> <input type="text" class="gf-form-input max-width-25"
<!-- <input type="text" class="input&#45;xlarge tight&#45;form&#45;input last" style="width: 450px" ng&#45;model="ctrl.panel.time_options" array&#45;join> --> ng-model="ctrl.panel.nowDelay" placeholder="0m"
<!-- </li> --> valid-time-span
<!-- </ul> --> bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
<!-- <div class="clearfix"></div> --> data-placement="right">
<!-- </div> -->
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 118px">
Auto-refresh
</li>
<li>
<input type="text" class="input-xlarge tight-form-input last" style="width: 450px" ng-model="ctrl.panel.refresh_intervals" array-join>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 118px">
Now delay
</li>
<li class="tight-form-item">
now-
</li>
<li>
<input type="text" class="input-mini tight-form-input last"
ng-model="ctrl.panel.nowDelay" placeholder="0m"
valid-time-span
bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
data-placement="right">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -129,8 +129,6 @@ export class TimePickerCtrl {
} }
setRelativeFilter(timespan) { setRelativeFilter(timespan) {
this.panel.now = true;
var range = {from: timespan.from, to: timespan.to}; var range = {from: timespan.from, to: timespan.to};
if (this.panel.nowDelay && range.to === 'now') { if (this.panel.nowDelay && range.to === 'now') {

View File

@ -1,90 +1,76 @@
<div class="editor-row"> <div class="editor-row">
<h5>Links and Dash Navigation</h5> <h5 class="section-heading">Links and Dash Navigation</h5>
<div ng-repeat="link in dashboard.links" style="margin-top: 10px;"> <div ng-repeat="link in dashboard.links">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<i ng-click="moveLink($index, -1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i>
<i ng-click="moveLink($index, 1)" ng-hide="$last" class="pointer fa fa-fw fa-arrow-down"></i>
</li>
<li class="tight-form-item last">
<i class="fa fa-remove pointer" ng-click="deleteLink($index)"></i>
</li>
</ul>
<ul class="tight-form-list"> <div class="gf-form-group">
<li class="tight-form-item" style="width: 20px"> <div class="gf-form-inline">
<i class="fa fa-fw fa-unlink"></i> <div class="gf-form">
</li> <span class="gf-form-label width-6">Type</span>
<div class="gf-form-select-wrapper width-10">
<select class="gf-form-input" ng-model="link.type" ng-options="f for f in ['dashboards','link']" ng-change="updated()"></select>
</div>
</div>
<li class="tight-form-item">Type</li> <div class="gf-form" ng-show="link.type === 'dashboards'">
<li> <span class="gf-form-label">With tags</span>
<select class="input-medium tight-form-input" style="width: 150px;" ng-model="link.type" ng-options="f for f in ['dashboards','link']" ng-change="updated()"></select> <bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags"></bootstrap-tagsinput>
</li> </div>
<li class="tight-form-item" ng-show="link.type === 'dashboards'">With tags</li> <div class="gf-form" ng-show="link.type === 'dashboards'">
<li ng-show="link.type === 'dashboards'">
<bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
</li>
<li class="tight-form-item" ng-show="link.type === 'dashboards'">
<editor-checkbox text="As dropdown" model="link.asDropdown" change="updated()"></editor-checkbox> <editor-checkbox text="As dropdown" model="link.asDropdown" change="updated()"></editor-checkbox>
</li> </div>
<li class="tight-form-item" ng-show="link.type === 'dashboards' && link.asDropdown">
Title <div class="gf-form max-width-30" ng-show="link.type === 'link'">
</li> <li class="gf-form-label width-6">Url</li>
<li ng-show="link.type === 'dashboards' && link.asDropdown"> <input type="text" ng-model="link.url" class="gf-form-input" ng-model-onblur ng-change="updated()">
<input type="text" ng-model="link.title" class="input-medium tight-form-input" ng-model-onblur ng-change="updated()"> </div>
</li>
<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 51px">Url</li> <div class="gf-form">
<li ng-show="link.type === 'link'"> <button class="btn btn-inverse btn-mini" ng-click="moveLink($index, -1)" ng-hide="$first"><i class="fa fa-arrow-up"></i></button>
<input type="text" ng-model="link.url" class="input-xlarge tight-form-input" style="width: 302px;" ng-model-onblur ng-change="updated()"> </div>
</li> <div class="gf-form">
<li class="tight-form-item" ng-show="link.type === 'link'"> <button class="btn btn-inverse btn-mini" ng-click="moveLink($index, 1)" ng-hide="$last"><i class="fa fa-arrow-down"></i></button>
</div>
<div class="gf-form">
<button class="btn btn-inverse btn-mini" ng-click="deleteLink($index)"><i class="fa fa-trash" ></i></button>
</div>
</div>
<div class="gf-form" ng-show="link.type === 'dashboards' && link.asDropdown">
<span class="gf-form-label width-6">Title</span>
<input type="text" ng-model="link.title" class="gf-form-input max-width-25" ng-model-onblur ng-change="updated()">
</div>
<div class="gf-form-inline" ng-show="link.type === 'link'">
<div class="gf-form">
<span class="gf-form-label width-6">Title</span>
<input type="text" ng-model="link.title" class="gf-form-input max-width-10" ng-model-onblur ng-change="updated()">
</div>
<div class="gf-form">
<span class="gf-form-label width-6">Tooltip</span>
<input type="text" ng-model="link.tooltip" class="gf-form-input max-width-10" placeholder="Open dashboard" ng-model-onblur ng-change="updated()">
</div>
<div class="gf-form">
<span class="gf-form-label width-6">Icon</span>
<div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="link.icon" ng-options="k as k for (k, v) in iconMap" ng-change="updated()"></select>
</div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-6">Include</span>
<editor-checkbox text="Time range" model="link.keepTime" change="updated()"></editor-checkbox>
<editor-checkbox text="Variable values" model="link.includeVars" change="updated()"></editor-checkbox>
<editor-checkbox text="Open in new tab " model="link.targetBlank" change="updated()"></editor-checkbox> <editor-checkbox text="Open in new tab " model="link.targetBlank" change="updated()"></editor-checkbox>
</li> </div>
</ul> </div>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="link.type === 'link'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 20px">
<i class="fa fa-fw fa-unlink invisible"></i>
</li>
<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 31px">Title</li>
<li ng-show="link.type === 'link'">
<input type="text" ng-model="link.title" class="input-medium tight-form-input" ng-model-onblur ng-change="updated()">
</li>
<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 51px">Tooltip</li>
<li ng-show="link.type === 'link'">
<input type="text" ng-model="link.tooltip" class="input-medium tight-form-input" style="width: 151px" placeholder="Open dashboard" ng-model-onblur ng-change="updated()">
</li>
<li class="tight-form-item" ng-show="link.type === 'link'">Icon</li>
<li ng-show="link.type === 'link'">
<select class="input-medium tight-form-input" style="width: 110px;" ng-model="link.icon" ng-options="k as k for (k, v) in iconMap" ng-change="updated()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 20px">
<i class="fa fa-fw fa-unlink invisible"></i>
</li>
<li class="tight-form-item">
<editor-checkbox text="Keep current time range" model="link.keepTime" change="updated()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Add current variable values" model="link.includeVars" change="updated()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row">
<br>
<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
</div>
<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>

View File

@ -15,6 +15,12 @@
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Name</span> <span class="gf-form-label width-7">Name</span>
<input class="gf-form-input max-width-21" type="text" ng-model="current.name" placeholder="My data source name" required> <input class="gf-form-input max-width-21" type="text" ng-model="current.name" placeholder="My data source name" required>
<gf-popover offset="0px -95px">
The name is used when you select the data source in panels.
The <code>Default</code> data source is preselected in new
panels.
</gf-popover>
<editor-checkbox text="Default" model="current.isDefault"></editor-checkbox> <editor-checkbox text="Default" model="current.isDefault"></editor-checkbox>
</div> </div>
@ -24,6 +30,7 @@
<select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="k as v.name for (k, v) in types" ng-change="typeChanged()"></select> <select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="k as v.name for (k, v) in types" ng-change="typeChanged()"></select>
</div> </div>
</div> </div>
</div> </div>
<rebuild-on-change property="datasourceMeta.id"> <rebuild-on-change property="datasourceMeta.id">

View File

@ -6,13 +6,27 @@
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Url</span> <span class="gf-form-label width-7">Url</span>
<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="http://my.server.com:8080" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input> <input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="http://my.server.com:8080" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
<gf-popover>
<p>Specify a complete HTTP url (http://your_server:8080)</p>
<span ng-show="current.access === 'direct'">
Your access method is <code>Direct</code>, this means the url
needs to be accessable from the browser.
</span>
<span ng-show="current.access === 'proxy'">
Your access method is currently <code>Proxy</code>, this means the url
needs to be accessable from the grafana backend.
</span>
</gf-popover>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7"> <span class="gf-form-label width-7">
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip> Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip>
</span> </span>
<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select> <div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
</div>
</div> </div>
<div class="gf-form"> <div class="gf-form">

View File

@ -22,8 +22,8 @@
<table class="filter-table" ng-if="ctrl.datasources.length > 0"> <table class="filter-table" ng-if="ctrl.datasources.length > 0">
<thead> <thead>
<tr> <tr>
<th><strong>Name</strong></th> <th><strong>name</strong></th>
<th><strong>Url</strong></th> <th><strong>url</strong></th>
<th style="width: 60px;"></th> <th style="width: 60px;"></th>
<th style="width: 85px;"></th> <th style="width: 85px;"></th>
<th style="width: 44px;"></th> <th style="width: 44px;"></th>

View File

@ -10,23 +10,16 @@
</button> </button>
</div> </div>
<div class="gf-box-body" style="min-height: 0px;"> <div class="gf-box-body">
<div class="tight-form last"> <div class="gf-form-group">
<ul class="tight-form-list"> <div class="gf-form">
<li class="tight-form-item"> <span class="gf-form-label">Key</span>
<strong>Key</strong> <span class="gf-form-label">{{key}}</span>
</li> </div>
<li class="tight-form-item last">
{{key}}
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<br>
<br>
<div class="grafana-info-box" style="text-align: left"> <div class="grafana-info-box" style="border: 0;">
You will only be able to view this key here once! It is not stored in this form. So be sure to copy it now. You will only be able to view this key here once! It is not stored in this form. So be sure to copy it now.
<br> <br>
<br> <br>

View File

@ -6,24 +6,16 @@
<h2 style="margin-top: 30px;">Add Organization</h2> <h2 style="margin-top: 30px;">Add Organization</h2>
<form name="form"> <form name="form" class="gf-form-group">
<div class="tight-form last"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Org. name</span>
<li class="tight-form-item" style="width: 100px;"> <input type="text" ng-model="newOrg.name" required class="gf-form-input" placeholder="organization name">
<strong>Org. name</strong>
</li>
<li>
<input type="text" ng-model="newOrg.name" required class="input-xxlarge tight-form-input last" placeholder="organization name">
</li>
<li>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<br> <br>
<button class="btn btn-success pull-right" ng-click="createOrg()">Create</button> <div class="gf-form-buttons-row">
<button class="btn btn-success pull-right" ng-click="createOrg()">Create</button>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
<navbar icon="icon-gf icon-gf-users" title="Organization"> <navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -1,4 +1,4 @@
<navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org"> <navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org/users">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -28,24 +28,27 @@ var panelTemplate = `
</div> </div>
<div class="panel-full-edit" ng-if="ctrl.editMode"> <div class="panel-full-edit" ng-if="ctrl.editMode">
<div class="gf-box"> <div class="tabbed-view tabbed-view--panel-edit">
<div class="gf-box-header"> <div class="tabbed-view-header">
<div class="gf-box-title"> <h2 class="tabbed-view-title">
<i ng-class="ctrl.icon"></i> <i ng-class="ctrl.icon"></i>
{{ctrl.pluginName}} {{ctrl.pluginName}}
</div> </h2>
<div ng-model="ctrl.editorTabIndex" bs-tabs> <ul class="gf-tabs">
<div ng-repeat="tab in ctrl.editorTabs" data-title="{{tab.title}}"> <li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
</div> <a class="gf-tabs-link" ng-click="ctrl.editorTabIndex = $index" ng-class="{active: ctrl.editorTabIndex === $index}">
</div> {{::tab.title}}
</a>
</li>
</ul>
<button class="gf-box-header-close-btn" ng-click="ctrl.exitFullscreen();"> <button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
Back to dashboard <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body"> <div class="tabbed-view-body">
<div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index"> <div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index">
<panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab> <panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
</div> </div>

View File

@ -1,59 +1,34 @@
<div class="editor-row"> <div class="gf-form-group">
<div class="section tight-form-container" style="margin-bottom: 20px"> <div class="gf-form">
<div class="tight-form"> <span class="gf-form-label">
<ul class="tight-form-list"> <i class="fa fa-clock-o"></i>
<li class="tight-form-item tight-form-item-icon"> </span>
<i class="fa fa-clock-o"></i>
</li> <span class="gf-form-label width-12">Override relative time</span>
<li class="tight-form-item" style="width: 178px"> <span class="gf-form-label width-6">Last</span>
<strong>Override relative time</strong>
</li> <input type="text" class="gf-form-input max-width-8" placeholder="1h"
<li class="tight-form-item" style="width: 50px"> empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span
Last ng-change="ctrl.refresh()" ng-model-onblur>
</li> </div>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h" <div class="gf-form">
empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span <span class="gf-form-label">
ng-change="ctrl.refresh()" ng-model-onblur> <i class="fa fa-clock-o"></i>
</li> </span>
</ul> <span class="gf-form-label width-12">Add time shift</span>
<div class="clearfix"></div> <span class="gf-form-label width-6">Amount</span>
</div> <input type="text" class="gf-form-input max-width-8" placeholder="1h"
<div class="tight-form"> empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
<ul class="tight-form-list"> ng-change="ctrl.refresh()" ng-model-onblur>
<li class="tight-form-item tight-form-item-icon"> </div>
<i class="fa fa-clock-o"></i>
</li> <div class="gf-form">
<li class="tight-form-item" style="width: 178px"> <span class="gf-form-label">
<strong>Add time shift</strong> <i class="fa fa-clock-o"></i>
</li> </span>
<li class="tight-form-item" style="width: 50px"> <editor-checkbox text="Hide time override info" model="ctrl.panel.hideTimeOverride" change="ctrl.refresh()"></editor-checkbox>
Amount
</li>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h"
empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
ng-change="ctrl.refresh()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-clock-o"></i>
</li>
<li class="tight-form-item" style="width: 178px">
<strong>Hide time override info</strong>
</li>
<li class="tight-form-item last">
<input class="cr1" id="ctrl.panel.hideTimeOverride" type="checkbox"
ng-model="ctrl.panel.hideTimeOverride" ng-checked="ctrl.panel.hideTimeOverride" ng-change="ctrl.refresh()">
<label for="ctrl.panel.hideTimeOverride" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>

View File

@ -1,84 +1,67 @@
<div class="editor-row"> <div class="editor-row">
<div class="section"> <h5 class="section-heading">
<h5>Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5> Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
</h5>
<div ng-repeat="link in panel.links" style="margin-top: 20px;"> <div ng-repeat="link in panel.links" style="margin-top: 20px;">
<div class="tight-form"> <div class="gf-form-group">
<ul class="tight-form-list pull-right">
<li class="tight-form-item">
<i ng-click="moveLink($index, -1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i>
<i ng-click="moveLink($index, 1)" ng-hide="$last" class="pointer fa fa-fw fa-arrow-down"></i>
</li>
<li class="tight-form-item last">
<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
</li>
</ul>
<ul class="tight-form-list"> <div class="gf-form-inline">
<li class="tight-form-item" style="width: 20px"> <div class="gf-form width-2">
<i class="fa fa-fw fa-unlink"></i> <i class="fa fa-fw fa-unlink"></i>
</li> </div>
<li class="tight-form-item">Type</li> <div class="gf-form">
<li> <span class="gf-form-label width-7">Type</span>
<select class="input-medium tight-form-input" style="width: 150px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select> <div class="gf-form-select-wrapper width-14">
</li> <select class="gf-form-input" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
</div>
</div>
<li class="tight-form-item" ng-show="link.type === 'dashboard'" style="width: 73px;">Dashboard</li> <div class="gf-form">
<li ng-show="link.type === 'dashboard'"> <span class="gf-form-label width-7" ng-show="link.type === 'dashboard'">Dashboard</span>
<input type="text" ng-model="link.dashboard" bs-typeahead="searchDashboards" class="input-large tight-form-input" ng-blur="dashboardChanged(link)"> <input ng-show="link.type === 'dashboard'" type="text" ng-model="link.dashboard" bs-typeahead="searchDashboards" class="gf-form-input max-width-14" ng-blur="dashboardChanged(link)">
</li>
<li class="tight-form-item" ng-show="link.type === 'absolute'" style="width: 73px;">Url</li> <span class="gf-form-label width-7" ng-show="link.type === 'absolute'">Url</span>
<li ng-show="link.type === 'absolute'"> <input ng-show="link.type === 'absolute'" type="text" ng-model="link.url" class="gf-form-input max-width-14">
<input type="text" ng-model="link.url" class="input-large tight-form-input"> </div>
</li>
</ul> <div class="gf-form">
<div class="clearfix"></div> <button class="btn-inverse gf-form-btn btn-small" ng-click="deleteLink(link)"><i class="fa fa-trash"></i></button>
</div>
</div> </div>
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list"> <div class="gf-form width-2">
<li class="tight-form-item" style="width: 20px"> <i class="fa fa-fw fa-unlink invisible"></i>
<i class="fa fa-fw fa-unlink invisible"></i> </div>
</li>
<li class="tight-form-item" style="width: 31px">Title</li> <div class="gf-form">
<li> <div class="gf-form-label width-7">Title</div>
<input type="text" ng-model="link.title" class="input-medium tight-form-input"> <input type="text" ng-model="link.title" class="gf-form-input">
</li> </div>
<li class="tight-form-item" style="width: 73px;">
Url params <div class="gf-form">
</li> <span class="gf-form-label width-7">Url params</span>
<li> <input type="text" ng-model="link.params" class="gf-form-input">
<input type="text" ng-model="link.params" class="input-large tight-form-input"> </div>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form last">
<ul class="tight-form-list"> <div class="gf-form-inline">
<li class="tight-form-item" style="width: 20px"> <div class="gf-form width-2">
<i class="fa fa-fw fa-unlink invisible"></i> <i class="fa fa-fw fa-unlink invisible"></i>
</li> </div>
<li class="tight-form-item">
<editor-checkbox text="Keep current time range" model="link.keepTime"></editor-checkbox> <div class="gf-form">
</li> <editor-checkbox text="Keep current time range" model="link.keepTime"></editor-checkbox>
<li class="tight-form-item"> <editor-checkbox text="Add current variable values" model="link.includeVars"></editor-checkbox>
<editor-checkbox text="Add current variable values" model="link.includeVars"></editor-checkbox> <editor-checkbox text="Open in new tab " model="link.targetBlank"></editor-checkbox>
</li> </div>
<li class="tight-form-item last">
<editor-checkbox text="Open in new tab " model="link.targetBlank"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row"> <div class="editor-row">
<br>
<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button> <button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
</div> </div>

View File

@ -52,6 +52,5 @@ function (angular, _) {
$scope.deleteLink = function(link) { $scope.deleteLink = function(link) {
$scope.panel.links = _.without($scope.panel.links, link); $scope.panel.links = _.without($scope.panel.links, link);
}; };
}); });
}); });

View File

@ -1,4 +1,4 @@
<navbar icon="fa fa-fw fa-list" title="Playlist"> <navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
</navbar> </navbar>
<div class="page-container" ng-form="playlistEditForm"> <div class="page-container" ng-form="playlistEditForm">
@ -7,6 +7,8 @@
<h1 ng-show="!ctrl.isNew()">Edit Playlist</h1> <h1 ng-show="!ctrl.isNew()">Edit Playlist</h1>
</div> </div>
<p class="playlist-description">A playlist rotates through a pre-selected list of Dashboards. A Playlist can be a great way to build situational awareness, or just show off your metrics to your team or visitors.</p>
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Name</span> <span class="gf-form-label width-7">Name</span>
@ -19,24 +21,29 @@
</div> </div>
<div class="gf-form-group"> <div class="gf-form-group">
<div class="max-width-28"> <h3 class="page-headering">Dashboards</h3>
<h5 class="page-headering">Add dashboards</h5>
<div style="">
<playlist-search class="playlist-search-container" search-started="ctrl.searchStarted(promise)"></playlist-search>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h5>Search results ({{ctrl.filteredDashboards.length + ctrl.filteredTags.length}})</h5> <div class="playlist-search-containerwrapper">
<div class="max-width-32">
<h5 class="page-headering playlist-column-header">Available</h5>
<div style="">
<playlist-search class="playlist-search-container" search-started="ctrl.searchStarted(promise)"></playlist-search>
</div>
</div>
</div>
<div ng-if="ctrl.filteredDashboards.length > 0"> <div ng-if="ctrl.filteredDashboards.length > 0">
<table class="grafana-options-table"> <table class="grafana-options-table playlist-available-list">
<tr ng-repeat="playlistItem in ctrl.filteredDashboards"> <tr ng-repeat="playlistItem in ctrl.filteredDashboards">
<td style="white-space: nowrap;"> <td>
{{playlistItem.title}} <i class="icon-gf icon-gf-dashboard"></i>
&nbsp;&nbsp;{{playlistItem.title}}
<i class="fa fa-star" ng-show="playlistItem.isStarred"></i>
</td> </td>
<td style="text-align: center"> <td class="add-dashboard">
<button class="btn btn-inverse btn-mini pull-right" ng-click="ctrl.addPlaylistItem(playlistItem)"> <button class="btn btn-inverse btn-mini pull-right" ng-click="ctrl.addPlaylistItem(playlistItem)">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Add to playlist Add to playlist
@ -46,31 +53,40 @@
</table> </table>
</div> </div>
<div class="playlist-search-results-container" ng-if="ctrl.filteredTags.length > 0;"> <div class="playlist-search-results-container" ng-if="ctrl.filteredTags.length > 0;">
<div ng-repeat="tag in ctrl.filteredTags" class="pointer" style="width: 180px; float: left;" <table class="grafana-options-table playlist-available-list">
ng-click="ctrl.addTagPlaylistItem(tag, $event)"> <tr ng-repeat="tag in ctrl.filteredTags">
<a class="search-result-tag label label-tag" tag-color-from-name="tag.term"> <td>
<i class="fa fa-tag"></i> <a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
<span>{{tag.term}} &nbsp;({{tag.count}})</span> <i class="fa fa-tag"></i>
</a> <span>{{tag.term}} &nbsp;({{tag.count}})</span>
</div> </a>
</div> </td>
<td class="add-dashboard">
<button class="btn btn-inverse btn-mini pull-right" ng-click="ctrl.addPlaylistItem(playlistItem)">
<i class="fa fa-plus"></i>
Add to playlist
</button>
</td>
</tr>
</table>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h5>Added dashboards</h5> <h5 class="page headering playlist-column-header">Selected</h5>
<table class="grafana-options-table"> <table class="grafana-options-table playlist-available-list">
<tr ng-repeat="playlistItem in ctrl.playlistItems"> <tr ng-repeat="playlistItem in ctrl.playlistItems">
<td style="white-space: nowrap;" ng-if="playlistItem.type === 'dashboard_by_id'"> <td ng-if="playlistItem.type === 'dashboard_by_id'">
{{playlistItem.title}} <i class="icon-gf icon-gf-dashboard"></i>&nbsp;&nbsp;{{playlistItem.title}}
</td> </td>
<td style="white-space: nowrap;" ng-if="playlistItem.type === 'dashboard_by_tag'"> <td ng-if="playlistItem.type === 'dashboard_by_tag'">
<a class="search-result-tag label label-tag" tag-color-from-name="playlistItem.title"> <a class="search-result-tag label label-tag" tag-color-from-name="playlistItem.title">
<i class="fa fa-tag"></i> <i class="fa fa-tag"></i>
<span>{{playlistItem.title}}</span> <span>{{playlistItem.title}}</span>
</a> </a>
</td> </td>
<td style="text-align: right"> <td class="selected-playlistitem-settings">
<button class="btn btn-inverse btn-mini" ng-hide="$first" ng-click="ctrl.movePlaylistItemUp(playlistItem)"> <button class="btn btn-inverse btn-mini" ng-hide="$first" ng-click="ctrl.movePlaylistItemUp(playlistItem)">
<i class="fa fa-arrow-up"></i> <i class="fa fa-arrow-up"></i>
</button> </button>
@ -89,7 +105,10 @@
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<a class="btn btn-success" <a class="btn btn-success " ng-show="ctrl.isNew()"
ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()"
ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Create new playlist</a>
<a class="btn btn-success" ng-show="!ctrl.isNew()"
ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()" ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()"
ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</a> ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</a>
<a class="btn-text" ng-click="ctrl.backToList()">Cancel</a> <a class="btn-text" ng-click="ctrl.backToList()">Cancel</a>

View File

@ -1,4 +1,4 @@
<navbar icon="fa fa-fw fa-list" title="Playlists"> <navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -11,7 +11,7 @@ export class PlaylistEditCtrl {
searchQuery: string = ''; searchQuery: string = '';
loading: boolean = false; loading: boolean = false;
playlist: any = { playlist: any = {
interval: '10m', interval: '5m',
}; };
playlistItems: any = []; playlistItems: any = [];
dashboardresult: any = []; dashboardresult: any = [];

View File

@ -0,0 +1,51 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
export class PluginEditCtrl {
model: any;
pluginId: any;
includedPanels: any;
includedDatasources: any;
tabIndex: number;
/** @ngInject */
constructor(private backendSrv: any, private $routeParams: any) {
this.model = {};
this.pluginId = $routeParams.pluginId;
this.tabIndex = 0;
this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(result => {
this.model = result;
this.includedPanels = _.where(result.includes, {type: 'panel'});
this.includedDatasources = _.where(result.includes, {type: 'datasource'});
});
}
update() {
var updateCmd = _.extend({
pluginId: this.model.pluginId,
orgId: this.model.orgId,
enabled: this.model.enabled,
pinned: this.model.pinned,
jsonData: this.model.jsonData,
secureJsonData: this.model.secureJsonData,
}, {});
this.backendSrv.post(`/api/org/plugins/${this.pluginId}/settings`, updateCmd).then(function() {
window.location.href = window.location.href;
});
}
toggleEnabled() {
this.update();
}
togglePinned() {
this.update();
}
}
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);

View File

@ -0,0 +1,17 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
export class PluginListCtrl {
plugins: any[];
/** @ngInject */
constructor(private backendSrv: any) {
this.backendSrv.get('api/org/plugins').then(plugins => {
this.plugins = plugins;
});
}
}
angular.module('grafana.controllers').controller('PluginListCtrl', PluginListCtrl);

View File

@ -0,0 +1,185 @@
<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
<a href="plugins/apps" class="navbar-page-btn">
<i class="fa fa-chevron-right"></i>
Apps
</a>
</navbar>
<div class="page-container">
<div class="plugin-header">
<span ng-show="ctrl.model.info.logos.large" class="plugin-header-logo">
<img src="{{ctrl.model.info.logos.large}}">
</span>
<div class="plugin-header-info-block">
<h1 class="plugin-header-name">{{ctrl.model.name}}</h1>
<div class="plugin-header-author">By {{ctrl.model.info.author.name}}</div>
<div class="plugin-header-stamps">
<span class="plugin-header-stamps-type">
<i class="icon-gf icon-gf-apps"></i> {{ctrl.model.type}}
</span>
</div>
</div>
</div>
<ul class="nav nav-tabs nav-tabs-alt">
<li ng-repeat="tab in ::['Overview', 'Details', 'Config']" ng-class="{active: ctrl.tabIndex === $index}">
<a ng-click="ctrl.tabIndex= $index">
{{::tab}}
</a>
</li>
</ul>
<div class="page-body">
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 0">
README.md
</div>
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 1">
Details
</div>
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 2">
<div class="gf-form-inline">
<div class="gf-form">
<editor-checkbox text="Enabled" model="ctrl.model.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
</div>
<div class="gf-form">
<editor-checkbox text="Pinned" model="ctrl.model.pinned" change="ctrl.togglePinned()"></editor-checkbox>
</div>
</div>
<div ng-if="ctrl.model.pluginId">
<plugin-component type="app-config-ctrl"></plugin-component>
<div class="clearfix"></div>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</div>
<aside class="page-sidebar">
<section class="page-sidebar-section">
<h4>Version</h4>
<span>1.0.1</span>
</section>
<section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
<h5>Includes</h4>
<ul class="ui-list">
<li ng-show="!ctrl.includedPanels.length"><em>None</em></li>
<li ng-repeat="panel in ctrl.includedPanels">
{{panel.name}}
</li>
<li ng-repeat="ds in ctrl.includedDatasources">
{{ds.name}}
</li>
<li ng-repeat="page in ctrl.model.pages">
<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
</li>
</ul>
</section>
<section class="page-sidebar-section">
<h5>Dependencies</h4>
<span>TODO</span>
</section>
<section class="page-sidebar-section">
<h5>Links</h4>
<ul class="ui-list">
<li ng-repeat="link in ctrl.model.info.links">
<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
</li>
</ul>
</section>
</aside>
</div>
</div>
</div>
<!-- <div class="app&#45;edit&#45;description"> -->
<!-- {{ctrl.model.info.description}}<br> -->
<!-- <span style="small"> -->
<!-- Version: {{ctrl.model.info.version}} &#38;nbsp; &#38;nbsp; Updated: {{ctrl.model.info.updated}} -->
<!-- </span> -->
<!-- </div> -->
<!-- -->
<!-- </div> -->
<!-- <div class="flex&#45;column"> -->
<!-- <ul class="app&#45;edit&#45;links"> -->
<!-- <li> -->
<!-- By <a href="{{ctrl.model.info.author.url}}" class="external&#45;link" target="_blank">{{ctrl.model.info.author.name}}</a> -->
<!-- </li> -->
<!-- <li ng&#45;repeat="link in ctrl.model.info.links"> -->
<!-- <a href="{{link.url}}" class="external&#45;link" target="_blank">{{link.name}}</a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- <section class="simple&#45;box"> -->
<!-- <h3 class="simple&#45;box&#45;header">Included with app:</h3> -->
<!-- <div class="flex&#45;container"> -->
<!-- <div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
<!-- <div class="simple&#45;box&#45;column&#45;header"> -->
<!-- <i class="fa fa&#45;th&#45;large"></i> -->
<!-- Dashboards -->
<!-- </div> -->
<!-- <ul> -->
<!-- <li><em class="small">None</em></li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- <div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
<!-- <div class="simple&#45;box&#45;column&#45;header"> -->
<!-- <i class="fa fa&#45;line&#45;chart"></i> -->
<!-- Panels -->
<!-- </div> -->
<!-- <ul> -->
<!-- <li ng&#45;show="!ctrl.includedPanels.length"><em class="small">None</em></li> -->
<!-- <li ng&#45;repeat="panel in ctrl.includedPanels"> -->
<!-- {{panel.name}} -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- <div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
<!-- <div class="simple&#45;box&#45;column&#45;header"> -->
<!-- <i class="fa fa&#45;database"></i> -->
<!-- Datasources -->
<!-- </div> -->
<!-- <ul> -->
<!-- <li ng&#45;show="!ctrl.includedDatasources.length"><em class="small">None</em></li> -->
<!-- <li ng&#45;repeat="ds in ctrl.includedDatasources"> -->
<!-- {{ds.name}} -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- <div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
<!-- <div class="simple&#45;box&#45;column&#45;header"> -->
<!-- <i class="fa fa&#45;files&#45;o"></i> -->
<!-- Pages -->
<!-- </div> -->
<!-- <ul> -->
<!-- <li ng&#45;repeat="page in ctrl.model.pages"> -->
<!-- <a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external&#45;link">{{page.name}}</a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- -->
<!-- </div> -->
<!-- </section> -->
<!-- -->
<!-- <section class="simple&#45;box"> -->
<!-- <h3 class="simple&#45;box&#45;header">Dependencies:</h3> -->
<!-- <div class="simple&#45;box&#45;body"> -->
<!-- Grafana 2.6.x -->
<!-- </div> -->
<!-- </section> -->
<!-- -->
<!-- <section class="simple&#45;box"> -->
<!-- <h3 class="simple&#45;box&#45;header">Configuration:</h3> -->
<!-- <div class="simple&#45;box&#45;body"> -->
<!-- <div ng&#45;if="ctrl.model.appId"> -->
<!-- <plugin&#45;component type="app&#45;config&#45;ctrl"></plugin&#45;component> -->
<!-- <div class="clearfix"></div> -->
<!-- <button type="submit" class="btn btn&#45;success" ng&#45;click="ctrl.update()">Save</button> -->
<!-- </div> -->
<!-- </div> -->
<!-- </section> -->
<!-- -->
<!-- -->
<!-- </div> -->

View File

@ -0,0 +1,42 @@
<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="plugins">
</navbar>
<div class="page-container">
<div class="page-header">
<h1>Plugins</h1>
</div>
<table class="filter-table">
<thead>
<tr>
<th><strong>Name</strong></th>
<th><strong>Type</strong></th>
<th style="width: 60px;"></th>
<th style="width: 80px;"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="plugin in ctrl.plugins">
<td>
<a href="plugins/{{plugin.pluginId}}/edit">
{{plugin.name}}
</a>
</td>
<td>
{{plugin.type}}
</td>
<td>
<span class="label label-info" ng-if="plugin.enabled">Enabled</span>
<span class="label label-info" ng-if="plugin.pinned">Pinned</span>
</td>
<td class="text-right">
<a href="plugins/{{plugin.pluginId}}/edit" class="btn btn-inverse btn-small">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,4 +1,4 @@
<navbar icon="icon-gf icon-gf-users" title="Profile"> <navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -1,40 +1,40 @@
<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots"> <navbar icon="icon-gf icon-gf-snapshot" title="Snapshots" title-url="dashboard/snapshots">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">
<div class="page-wide"> <div class="page-header">
<h1>Available snapshots</h1> <h1>Available snapshots</h1>
<table class="filter-table" style="margin-top: 20px">
<thead>
<th><strong>Name</strong></th>
<th><strong>Snapshot url</strong></th>
<th style="width: 70px"></th>
<th style="width: 25px"></th>
</thead>
<tr ng-repeat="snapshot in ctrl.snapshots">
<td>
<a href="dashboard/snapshot/{{snapshot.key}}">{{snapshot.name}}</a>
</td>
<td >
<a href="dashboard/snapshot/{{snapshot.key}}">dashboard/snapshot/{{snapshot.key}}</a>
</td>
<td class="text-center">
<a href="dashboard/snapshot/{{snapshot.key}}" class="btn btn-inverse btn-mini">
<i class="fa fa-eye"></i>
View
</a>
</td>
<td class="text-right">
<a ng-click="ctrl.removeSnapshot(snapshot)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div> </div>
<table class="filter-table" style="margin-top: 20px">
<thead>
<th><strong>Name</strong></th>
<th><strong>Snapshot url</strong></th>
<th style="width: 70px"></th>
<th style="width: 25px"></th>
</thead>
<tr ng-repeat="snapshot in ctrl.snapshots">
<td>
<a href="dashboard/snapshot/{{snapshot.key}}">{{snapshot.name}}</a>
</td>
<td >
<a href="dashboard/snapshot/{{snapshot.key}}">dashboard/snapshot/{{snapshot.key}}</a>
</td>
<td class="text-center">
<a href="dashboard/snapshot/{{snapshot.key}}" class="btn btn-inverse btn-mini">
<i class="fa fa-eye"></i>
View
</a>
</td>
<td class="text-right">
<a ng-click="ctrl.removeSnapshot(snapshot)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div> </div>

View File

@ -1,4 +1,4 @@
<navbar icon="fa fa-fw fa-adjust" title="Style Guide"> <navbar icon="fa fa-fw fa-adjust" title="Style Guide" title-url="styleguide">
</navbar> </navbar>
<div class="page-container"> <div class="page-container">

View File

@ -1,332 +1,218 @@
<div ng-controller="TemplateEditorCtrl" ng-init="init()"> <div ng-controller="TemplateEditorCtrl" ng-init="init()">
<div class="gf-box-header"> <div class="tabbed-view-header">
<div class="gf-box-title"> <h2 class="tabbed-view-title">
<i class="fa fa-code"></i>
Templating Templating
</div> </h2>
<div class="tabs"> <ul class="gf-tabs">
<ul class="nav nav-tabs"> <li class="gf-tabs-item" >
<li ng-class="{active: mode === 'list'}"> <a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
<a ng-click="mode = 'list';"> Variables
Variables </a>
</a> </li>
</li> <li class="gf-tabs-item" ng-show="mode === 'edit'">
<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
{{current.name}}
</a>
</li>
<li class="gf-tabs-item" ng-show="mode === 'new'">
<span class="active gf-tabs-link">New</span>
</li>
</ul>
<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'"> <button class="tabbed-view-close-btn" ng-click="dismiss();dashboard.refresh();">
<a>
{{current.name}}
</a>
</li>
<li ng-class="{active: mode === 'new'}">
<a ng-click="mode = 'new';">
<i class="fa fa-plus"></i>
New
</a>
</li>
</ul>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body"> <div class="tabbed-view-body">
<div ng-if="mode === 'list'"> <div ng-if="mode === 'list'">
<div ng-if="variables.length === 0">
<em>No template variables defined</em>
</div>
<table class="grafana-options-table">
<tr ng-repeat="variable in variables">
<td style="width: 1%">
<span class="template-variable">
${{variable.name}}
</span>
</td>
<td class="max-width" style="max-width: 200px;">
{{variable.query}}
</td>
<div class="editor-row row"> <td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<div style="max-width: 1024px"> <td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<div ng-if="variables.length === 0"> <td style="width: 1%">
<em>No template variables defined</em> <a ng-click="duplicate(variable)" class="btn btn-inverse btn-mini">
</div> Duplicate
<table class="grafana-options-table"> </a>
<tr ng-repeat="variable in variables"> </td>
<td style="width: 1%"> <td style="width: 1%">
<span class="template-variable"> <a ng-click="edit(variable)" class="btn btn-inverse btn-mini">
${{variable.name}} <i class="fa fa-edit"></i>
</span> Edit
</td> </a>
<td class="max-width" style="max-width: 200px;"> </td>
{{variable.query}} <td style="width: 1%">
</td> <a ng-click="removeVariable(variable)" class="btn btn-danger btn-mini">
<td style="width: 1%"> <i class="fa fa-remove"></i>
<a ng-click="edit(variable)" class="btn btn-inverse btn-small"> </a>
<i class="fa fa-edit"></i> </td>
Edit </tr>
</a> </table>
</td> </div>
<td style="width: 1%">
<a ng-click="duplicate(variable)" class="btn btn-inverse btn-small"> <div class="gf-form" ng-show="mode === 'list'">
Duplicate <div class="gf-form-button-row">
</a> <a type="button" class="btn gf-form-button btn-success" ng-click="mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
</td>
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%">
<a ng-click="removeVariable(variable)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
<div ng-if="mode === 'edit' || mode === 'new'"> <div ng-if="mode === 'edit' || mode === 'new'">
<div class="editor-row"> <h5 class="section-heading">Variable</h5>
<div class="tight-form-section"> <div class="gf-form-group">
<h5>Variable</h5> <div class="gf-form-inline">
<div class="tight-form last"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-7">Name</span>
<li class="tight-form-item" style="width: 100px"> <input type="text" class="gf-form-input max-width-14" placeholder="name" ng-model='current.name'></input>
Name </div>
</li> <div class="gf-form">
<li> <span class="gf-form-label width-7">Type</span>
<input type="text" class="input-large tight-form-input" placeholder="name" ng-model='current.name'></input> <div class="gf-form-select-wrapper max-width-10">
</li> <select class="gf-form-input max-width-10" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
<li class="tight-form-item"> </div>
Type </div>
</li> <div class="gf-form">
<li> <span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span>
<select class="input-small tight-form-input" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select> <div class="gf-form-select-wrapper" ng-show="current.type === 'query'">
</li> <select class="gf-form-input max-width-14" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
<li class="tight-form-item" ng-show="current.type === 'query'"> </div>
Data source </div>
</li> </div>
<li ng-show="current.type === 'query'"> <div class="gf-form">
<select class="input input-medium tight-form-input last" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select> <span class="gf-form-label width-7">Label</span>
</li> <input type="text" class="gf-form-input max-width-14" ng-model='current.label' placeholder="optional display name"></input>
</ul> <editor-checkbox class="width-13" text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
<div class="clearfix"></div> </div>
</div>
<h5 class="section-heading">Value Options</h5>
<div ng-show="current.type === 'interval'" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-7">Values</span>
<input type="text" class="gf-form-input max-width-28" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
</div>
<div class="gf-form">
<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox>
<span class="gf-form-label" ng-show="current.auto">
Auto interval steps <tip>How many times should the current time range be divided to calculate the value</tip>
</span>
<div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
<select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label" ng-show="current.auto">
Auto interval min value <tip>The calculated value will not go below this threshold</tip>
</span>
<input type="text" class="gf-form-input max-width-10" ng-show="current.auto" ng-model="current.auto_min" ng-change="runQuery()"></input>
</div>
</div>
<div ng-show="current.type === 'custom'" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-13">Values seperated by comma</span>
<input type="text" class="gf-form-input max-width-22" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
</div>
<div class="gf-form ">
<editor-checkbox class="width-13" text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
<input ng-show="current.includeAll" type="text" class="gf-form-input max-width-22" ng-model='current.options[0].value' style="margin-left: 4px;"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-13" ng-show="current.includeAll">All format</span>
<div class="gf-form-select-wrapper max-width-10" ng-show="current.includeAll">
<select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row"> <div ng-show="current.type === 'query'" class="gf-form-group">
<div class="tight-form-section"> <div class="gf-form">
<h5>Value Options</h5> <span class="gf-form-label width-7">Query</span>
<input type="text" class="gf-form-input" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
<div ng-show="current.type === 'interval'"> </div>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-7">
<li class="tight-form-item" style="width: 160px"> Regex
Values <tip>Optional, if you want to extract part of a series name or metric node segment</tip>
</li> </span>
<li> <input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
<input type="text" style="width: 345px" class="input-xxlarge tight-form-input last" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input> </div>
</li> <div class="gf-form">
</ul> <span class="gf-form-label width-7">All value</span>
<div class="clearfix"></div> <editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
</div> </div>
<div class="tight-form last"> <div class="gf-form-inline" ng-show="current.includeAll">
<ul class="tight-form-list"> <div class="gf-form">
<li class="tight-form-item" style="width: 160px"> <span class="gf-form-label width-7">All format</span>
<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox> <div class="gf-form-select-wrapper">
</li> <select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
<li class="tight-form-item" ng-show="current.auto">
Auto interval steps <tip>How many times should the current time range be divided to calculate the value</tip>
</li>
<li>
<select class="input-mini tight-form-input last" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
</li>
<li class="tight-form-item" ng-show="current.auto">
Auto interval min value <tip>The calculated value will not go below this threshold</tip>
</li>
<li>
<input type="text" style="width: 35px" class="input-xxlarge tight-form-input last" ng-model="current.auto_min" ng-change="runQuery()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
<div class="gf-form max-width-30">
<div ng-show="current.type === 'custom'"> <span class="gf-form-label width-7">All value</span>
<div class="tight-form last"> <input type="text" class="gf-form-input" ng-model='current.options[0].value'></input>
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 180px">
Values seperated by comma
</li>
<li>
<input type="text" class="input tight-form-input last" style="width: 325px;" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
<editor-checkbox text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
</li>
<li ng-show="current.includeAll">
<input type="text" class="input-xlarge tight-form-input" style="width:364px" ng-model='current.options[0].value'></input>
</li>
<li class="tight-form-item" ng-show="current.includeAll">
All format
</li>
<li ng-show="current.includeAll">
<select class="input-medium tight-form-input last" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Update</span>
<editor-checkbox text="On Dashboard Load" model="current.refresh"></editor-checkbox>
<tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time</tip>
</div>
</div>
<div ng-show="current.type === 'query'"> <div class="gf-form-group" >
<h5 class="section-heading">Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
<li class="tight-form-item" style="width: 100px"> <span class="gf-form-label" ng-show="current.multi">Multi format</span>
Query <div class="gf-form-select-wrapper max-width-10" ng-show="current.multi">
</li> <select class="gf-form-input" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
<li>
<input type="text" style="width: 588px" class="input-xxlarge tight-form-input last" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
Regex
<tip>Optional, if you want to extract part of a series name or metric node segment</tip>
</li>
<li>
<input type="text" style="width: 588px" class="input tight-form-input last" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
<editor-checkbox text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
</li>
<li ng-show="current.includeAll">
<input type="text" class="input-xlarge tight-form-input" style="width:364px" ng-model='current.options[0].value'></input>
</li>
<li class="tight-form-item" ng-show="current.includeAll">
All format
</li>
<li ng-show="current.includeAll">
<select class="input-medium tight-form-input last" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item last">
<editor-checkbox text="Refresh on load" model="current.refresh"></editor-checkbox>
<tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time</tip>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row"> <div class="gf-form-group" ng-if="current.type === 'query'">
<div class="tight-form-section" ng-hide="current.type === 'interval'"> <h5>Value groups/tags (Experimental feature)</h5>
<h5>Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5> <div class="gf-form">
<div class="tight-form last"> <editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
<ul class="tight-form-list">
<li class="tight-form-item last" style="width: 100px;">
<editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
</li>
<li class="tight-form-item" ng-show="current.multi">
Multi format
</li>
<li ng-show="current.multi">
<select class="input-medium tight-form-input last" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
<div class="gf-form last" ng-if="current.useTags">
<div class="tight-form-section"> <span class="gf-form-label width-10">Tags query</span>
<h5>Display options</h5> <input type="text" class="gf-form-input" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
<div class="tight-form last"> </div>
<ul class="tight-form-list"> <div class="gf-form" ng-if="current.useTags">
<li class="tight-form-item" style="width: 100px"> <li class="gf-form-label width-10">Tag values query</li>
Variable Label <input type="text" class="gf-form-input" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model='current.label' placeholder=""></input>
</li>
<li class="tight-form-item last">
<editor-checkbox text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
<div class="editor-row" ng-if="current.type === 'query'"> <div class="gf-form-group">
<div class="tight-form-section"> <h5>Preview of values (shows max 20)</h5>
<h5>Value groups/tags (Experimental feature)</h5> <div class="gf-form">
<div class="tight-form last" ng-if="current.useTags"> <span class="gf-form-label" ng-repeat="option in current.options | limitTo: 20">
<ul class="tight-form-list"> {{option.text}}
<li class="tight-form-item" style="width: 135px"> </span>
Tags query
</li>
<li>
<input type="text" style="width: 588px" class="input-xxlarge tight-form-input last" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="current.useTags">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 135px;">
Tag values query
</li>
<li>
<input type="text" style="width: 588px" class="input tight-form-input last" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item last">
<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="editor-row">
<div class="tight-form-section">
<h5>Preview of values (shows max 20)</h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" ng-repeat="option in current.options | limitTo: 20">
{{option.text}}
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-row" style="margin-top: 20px"> <div class="gf-form-button-row p-y-0">
<button type="button" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button> <button type="button" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button>
<button type="button" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button> <button type="button" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -39,4 +39,12 @@ declare module 'app/core/store' {
export default store; export default store;
} }
declare module 'tether' {
var config: any;
export default config;
}
declare module 'tether-drop' {
var config: any;
export default config;
}

View File

@ -1,5 +1,6 @@
<div> <div>
<ul class="nav nav-{{type || 'tabs'}} nav-tabs-alt" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude></ul> <ul class="nav nav-tabs" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude>
</ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" <div class="tab-pane"
ng-repeat="tab in tabs" ng-repeat="tab in tabs"

View File

@ -1,11 +0,0 @@
<div class="graph-legend-popover">
<a class="close" ng-click="dismiss();" href="">×</a>
<div class="editor-row">
<i ng-repeat="color in colors" class="pointer fa fa-circle"
ng-style="{color:color}"
ng-click="colorSelected(color);dismiss();">&nbsp;</i>
</div>
</div>

View File

@ -1,22 +1,21 @@
<div ng-controller="JsonEditorCtrl"> <div ng-controller="JsonEditorCtrl">
<div class="tabbed-view-header">
<h2 class="tabbed-view-title">
JSON
</h2>
<div class="gf-box-header"> <button class="tabbed-view-close-btn" ng-click="dismiss()">
<div class="gf-box-title">
<i class="fa fa-edit"></i>
JSON
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body" style="height: 500px"> <div class="tabbed-view-body">
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 100%;"></textarea> <div class="gf-form">
<br> <textarea class="gf-form-input" ng-model="json" rows="20" spellcheck="false"></textarea>
<br> </div>
<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button> <div class="gf-form-button-row">
<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
</div>
</div> </div>
</div> </div>

View File

@ -32,6 +32,10 @@
<td><span class="label label-info">CTRL+S</span></td> <td><span class="label label-info">CTRL+S</span></td>
<td>Save dashboard</td> <td>Save dashboard</td>
</tr> </tr>
<tr>
<td><span class="label label-info">CTRL+E</span></td>
<td>Export dashboard</td>
</tr>
<tr> <tr>
<td><span class="label label-info">CTRL+H</span></td> <td><span class="label label-info">CTRL+H</span></td>
<td>Hide row controls</td> <td>Hide row controls</td>
@ -40,6 +44,10 @@
<td><span class="label label-info">CTRL+Z</span></td> <td><span class="label label-info">CTRL+Z</span></td>
<td>Zoom out</td> <td>Zoom out</td>
</tr> </tr>
<tr>
<td><span class="label label-info">CTRL+I</span></td>
<td>Quick snapshot</td>
</tr>
<tr> <tr>
<td><span class="label label-info">CTRL+O</span></td> <td><span class="label label-info">CTRL+O</span></td>
<td>Enable/Disable shared graph crosshair</td> <td>Enable/Disable shared graph crosshair</td>

View File

@ -8,7 +8,7 @@
<span class="gf-form-label width-6">Span</span> <span class="gf-form-label width-6">Span</span>
<select class="gf-form-input gf-size-auto" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select> <select class="gf-form-input gf-size-auto" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
</div> </div>
<div class="gf-form max-width-21"> <div class="gf-form max-width-26">
<span class="gf-form-label width-8">Height</span> <span class="gf-form-label width-8">Height</span>
<input type="text" class="gf-form-input max-width-6" ng-model='ctrl.panel.height' placeholder="100px"></input> <input type="text" class="gf-form-input max-width-6" ng-model='ctrl.panel.height' placeholder="100px"></input>
<editor-checkbox text="Transparent" model="ctrl.panel.transparent"></editor-checkbox> <editor-checkbox text="Transparent" model="ctrl.panel.transparent"></editor-checkbox>
@ -31,10 +31,6 @@
</div> </div>
</div> </div>
<br>
<br>
<br>
<panel-links-editor panel="ctrl.panel"></panel-links-editor> <panel-links-editor panel="ctrl.panel"></panel-links-editor>

View File

@ -1,11 +1,10 @@
<div class="container"> <div class="login-container container">
<div class="login-box"> <div class="login-box">
<div class="login-box-logo"> <div class="login-box-logo">
<a href="login"> <img class="logo-icon" src="public/img/grafana_icon.svg"></img><br>
<img src="img/logo_transparent_200x75.png"> <i class="icon-gf icon-gf-grafana_wordmark"></i>
</a>
</div> </div>
<div class="login-inner-box"> <div class="login-inner-box">
@ -15,76 +14,50 @@
</button> </button>
</div> </div>
<form name="sendResetForm" class="login-form" ng-show="mode === 'send'"> <form name="sendResetForm" class="login-form gf-form-group" ng-show="mode === 'send'">
<div class="tight-form last"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-7">User</span>
<li class="tight-form-item" style="width: 78px"> <input type="text" name="username" class="gf-form-input max-width-14" required ng-model='formModel.userOrEmail' placeholder="email or username">
<strong>User</strong>
</li>
<li>
<input type="text" name="username" class="tight-form-input last" required ng-model='formModel.userOrEmail' placeholder="email or username" style="width: 253px">
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="login-submit-button-row"> <div class="gf-form-button-row">
<button type="submit" class="btn" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}"> <button type="submit" class="btn btn-large" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}">
Send reset instructions Send reset instructions
</button> </button>
</div> </div>
</form> </form>
<h5 ng-if="mode === 'email-sent'" style="text-align: center; padding: 20px;"> <h5 style="text-align: center; padding: 20px;" ng-if="mode === 'email-sent'">
An email with a reset link as been sent to the email address, you should receive it shortly. An email with a reset link as been sent to the email address, you should receive it shortly.
</h5> </h5>
<form name="resetForm" class="login-form" ng-show="mode === 'reset'"> <form name="resetForm" class="login-form gf-form-group" ng-show="mode === 'reset'">
<div class="tight-form"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">New Password</span>
<li class="tight-form-item" style="width: 125px"> <input type="password" name="NewPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.newPassword' placeholder="password" watch-change="formModel.newPassword = inputValue;">
<strong>New Password</strong>
</li>
<li>
<input type="password" name="NewPassword" class="tight-form-input last" required ng-minlength="4" ng-model='formModel.newPassword' placeholder="password" style="width: 207px" watch-change="formModel.newPassword = inputValue;">
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form last"> <div class="gf-form">
<ul class="tight-form-list"> <span class="gf-form-label width-10">Confirm Password</span>
<li class="tight-form-item" style="width: 125px"> <input type="password" name="ConfirmPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password">
<strong>Confirm Password</strong>
</li>
<li>
<input type="password" name="ConfirmPassword" class="tight-form-input last" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password" style="width: 207px">
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div style="margin-left: 141px; width: 207px;"> <div style="margin-left: 141px; width: 207px;">
<password-strength password="formModel.newPassword"></password-strength> <password-strength password="formModel.newPassword"></password-strength>
</div> </div>
<div class="login-submit-button-row"> <div class="gf-form-button-row">
<button type="submit" class="btn" ng-click="submitReset();" ng-class="{'btn-inverse': !resetForm.$valid, 'btn-primary': resetForm.$valid}"> <button type="submit" class="btn" ng-click="submitReset();" ng-class="{'btn-inverse': !resetForm.$valid, 'btn-primary': resetForm.$valid}">
Reset Password Reset Password
</button> </button>
</div> </div>
</form> </form>
<div class="clearfix"></div>
</div> </div>
<div class="row" style="margin-top: 40px"> <div class="row" style="margin-top: 20px">
<div class="text-center"> <div class="text-center">
<a href="login"> <a href="login">Back to login</a>
Back to login
</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,64 +1,47 @@
<div class="tabbed-view-header">
<div class="gf-box-header"> <h2 class="tabbed-view-title">
<div class="gf-box-title">
<i class="fa fa-th-list"></i>
Row settings Row settings
</div> </h2>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;"> <button class="tabbed-view-close-btn" ng-click="dismiss();">
<div ng-repeat="tab in ['General']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</button> </button>
</div> </div>
<div class="gf-box-body"> <div class="tabbed-view-body">
<div class="row">
<div class="col-md-8">
<div class="page-heading">
<h5>Row details</h5>
</div>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="editor-row"> <div class="gf-form">
<div class="section"> <span class="gf-form-label width-6">Title</span>
<h5>Row details</h5> <input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
<div class="tight-form last"> </div>
<ul class="tight-form-list"> <div class="gf-form">
<li class="tight-form-item"> <span class="gf-form-label width-6">Height</span>
Title <input type="text" class="gf-form-input max-width-8" ng-model='row.height'></input>
</li> <editor-checkbox text="Show Title" model="row.showTitle"></editor-checkbox>
<li> </div>
<input type="text" class="input-xlarge tight-form-input" ng-model='row.title'></input> </div>
</li>
<li class="tight-form-item">
Height
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model='row.height'></input>
</li>
<li class="tight-form-item last">
<label class="checkbox-label" for="row.showTitle">Show Title</label>
<input class="cr1" id="row.showTitle" type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle">
<label for="row.showTitle" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
<div class="section"> <div class="col-md-4">
<h5>Templating options</h5> <div class="page-heading">
<div class="tight-form last"> <h5>Templating options</h5>
<ul class="tight-form-list"> </div>
<li class="tight-form-item"> <div class="gf-form-group">
Repeat Row <div class="gf-form">
</li> <span class="gf-form-label">Repeat Row</span>
<li> <div class="gf-form-select-wrapper max-width-10">
<select class="input-small tight-form-input last" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list"> <select class="gf-form-input" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
<option value=""></option> <option value=""></option>
</select> </div>
</li> </div>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="variable-link-wrapper"> <div class="variable-link-wrapper">
<a ng-click="vm.show()" class="variable-value-link tight-form-item"> <a ng-click="vm.show()" class="variable-value-link">
{{vm.linkText}} {{vm.linkText}}
<span ng-repeat="tag in vm.selectedTags" bs-tooltip='tag.valuesText' data-placement="bottom"> <span ng-repeat="tag in vm.selectedTags" bs-tooltip='tag.valuesText' data-placement="bottom">
<span class="label-tag"tag-color-from-name="tag.text"> <span class="label-tag"tag-color-from-name="tag.text">
@ -10,7 +10,7 @@
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</a> </a>
<input type="text" class="tight-form-clear-input input-small" style="display: none" ng-keydown="vm.keyDown($event)" ng-model="vm.search.query" ng-change="vm.queryChanged()" ></input> <input type="text" class="hidden-input input-small" style="display: none" ng-keydown="vm.keyDown($event)" ng-model="vm.search.query" ng-change="vm.queryChanged()" ></input>
<div class="variable-value-dropdown" ng-if="vm.dropdownVisible" ng-class="{'multi': vm.variable.multi, 'single': !vm.variable.multi}"> <div class="variable-value-dropdown" ng-if="vm.dropdownVisible" ng-class="{'multi': vm.variable.multi, 'single': !vm.variable.multi}">
<div class="variable-options-wrapper"> <div class="variable-options-wrapper">

View File

@ -0,0 +1,2 @@
declare var test: any;
export default test;

View File

@ -0,0 +1,105 @@
define([
'lodash',
],
function (_) {
'use strict';
function CloudWatchAnnotationQuery(datasource, annotation, $q, templateSrv) {
this.datasource = datasource;
this.annotation = annotation;
this.$q = $q;
this.templateSrv = templateSrv;
}
CloudWatchAnnotationQuery.prototype.process = function(from, to) {
var self = this;
var usePrefixMatch = this.annotation.prefixMatching;
var region = this.templateSrv.replace(this.annotation.region);
var namespace = this.templateSrv.replace(this.annotation.namespace);
var metricName = this.templateSrv.replace(this.annotation.metricName);
var dimensions = this.datasource.convertDimensionFormat(this.annotation.dimensions);
var statistics = _.map(this.annotation.statistics, function(s) { return self.templateSrv.replace(s); });
var defaultPeriod = usePrefixMatch ? '' : '300';
var period = this.annotation.period || defaultPeriod;
period = parseInt(period, 10);
var actionPrefix = this.annotation.actionPrefix || '';
var alarmNamePrefix = this.annotation.alarmNamePrefix || '';
var d = this.$q.defer();
var allQueryPromise;
if (usePrefixMatch) {
allQueryPromise = [
this.datasource.performDescribeAlarms(region, actionPrefix, alarmNamePrefix, [], '').then(function(alarms) {
alarms.MetricAlarms = self.filterAlarms(alarms, namespace, metricName, dimensions, statistics, period);
return alarms;
})
];
} else {
if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return this.$q.when([]); }
allQueryPromise = _.map(statistics, function(statistic) {
return self.datasource.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
});
}
this.$q.all(allQueryPromise).then(function(alarms) {
var eventList = [];
var start = self.datasource.convertToCloudWatchTime(from, false);
var end = self.datasource.convertToCloudWatchTime(to, true);
_.chain(alarms)
.pluck('MetricAlarms')
.flatten()
.each(function(alarm) {
if (!alarm) {
d.resolve(eventList);
return;
}
self.datasource.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
_.each(history.AlarmHistoryItems, function(h) {
var event = {
annotation: self.annotation,
time: Date.parse(h.Timestamp),
title: h.AlarmName,
tags: [h.HistoryItemType],
text: h.HistorySummary
};
eventList.push(event);
});
d.resolve(eventList);
});
});
});
return d.promise;
};
CloudWatchAnnotationQuery.prototype.filterAlarms = function(alarms, namespace, metricName, dimensions, statistics, period) {
return _.filter(alarms.MetricAlarms, function(alarm) {
if (!_.isEmpty(namespace) && alarm.Namespace !== namespace) {
return false;
}
if (!_.isEmpty(metricName) && alarm.MetricName !== metricName) {
return false;
}
var sd = function(d) {
return d.Name;
};
var isSameDimensions = JSON.stringify(_.sortBy(alarm.Dimensions, sd)) === JSON.stringify(_.sortBy(dimensions, sd));
if (!_.isEmpty(dimensions) && !isSameDimensions) {
return false;
}
if (!_.isEmpty(statistics) && !_.contains(statistics, alarm.Statistic)) {
return false;
}
if (!_.isNaN(period) && alarm.Period !== period) {
return false;
}
return true;
});
};
return CloudWatchAnnotationQuery;
});

View File

@ -3,8 +3,9 @@ define([
'lodash', 'lodash',
'moment', 'moment',
'app/core/utils/datemath', 'app/core/utils/datemath',
'./annotation_query',
], ],
function (angular, _, moment, dateMath) { function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
'use strict'; 'use strict';
/** @ngInject */ /** @ngInject */
@ -15,9 +16,10 @@ function (angular, _, moment, dateMath) {
this.proxyUrl = instanceSettings.url; this.proxyUrl = instanceSettings.url;
this.defaultRegion = instanceSettings.jsonData.defaultRegion; this.defaultRegion = instanceSettings.jsonData.defaultRegion;
var self = this;
this.query = function(options) { this.query = function(options) {
var start = convertToCloudWatchTime(options.range.from, false); var start = self.convertToCloudWatchTime(options.range.from, false);
var end = convertToCloudWatchTime(options.range.to, true); var end = self.convertToCloudWatchTime(options.range.to, true);
var queries = []; var queries = [];
options = angular.copy(options); options = angular.copy(options);
@ -30,7 +32,7 @@ function (angular, _, moment, dateMath) {
query.region = templateSrv.replace(target.region, options.scopedVars); query.region = templateSrv.replace(target.region, options.scopedVars);
query.namespace = templateSrv.replace(target.namespace, options.scopedVars); query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
query.metricName = templateSrv.replace(target.metricName, options.scopedVars); query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
query.dimensions = convertDimensionFormat(target.dimensions, options.scopedVars); query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
query.statistics = target.statistics; query.statistics = target.statistics;
var range = end - start; var range = end - start;
@ -117,7 +119,7 @@ function (angular, _, moment, dateMath) {
parameters: { parameters: {
namespace: templateSrv.replace(namespace), namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName), metricName: templateSrv.replace(metricName),
dimensions: convertDimensionFormat(filterDimensions, {}), dimensions: this.convertDimensionFormat(filterDimensions, {}),
} }
}; };
@ -206,6 +208,14 @@ function (angular, _, moment, dateMath) {
return $q.when([]); return $q.when([]);
}; };
this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
return this.awsRequest({
region: region,
action: 'DescribeAlarms',
parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
});
};
this.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,
@ -223,55 +233,8 @@ function (angular, _, moment, dateMath) {
}; };
this.annotationQuery = function(options) { this.annotationQuery = function(options) {
var annotation = options.annotation; var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
var region = templateSrv.replace(annotation.region); return annotationQuery.process(options.range.from, options.range.to);
var namespace = templateSrv.replace(annotation.namespace);
var metricName = templateSrv.replace(annotation.metricName);
var dimensions = convertDimensionFormat(annotation.dimensions);
var statistics = _.map(annotation.statistics, function(s) { return templateSrv.replace(s); });
var period = annotation.period || '300';
period = parseInt(period, 10);
if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return $q.when([]); }
var d = $q.defer();
var self = this;
var allQueryPromise = _.map(statistics, function(statistic) {
return self.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
});
$q.all(allQueryPromise).then(function(alarms) {
var eventList = [];
var start = convertToCloudWatchTime(options.range.from, false);
var end = convertToCloudWatchTime(options.range.to, true);
_.chain(alarms)
.pluck('MetricAlarms')
.flatten()
.each(function(alarm) {
if (!alarm) {
d.resolve(eventList);
return;
}
self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
_.each(history.AlarmHistoryItems, function(h) {
var event = {
annotation: annotation,
time: Date.parse(h.Timestamp),
title: h.AlarmName,
tags: [h.HistoryItemType],
text: h.HistorySummary
};
eventList.push(event);
});
d.resolve(eventList);
});
});
});
return d.promise;
}; };
this.testDatasource = function() { this.testDatasource = function() {
@ -347,21 +310,21 @@ function (angular, _, moment, dateMath) {
}); });
} }
function convertToCloudWatchTime(date, roundUp) { this.convertToCloudWatchTime = function(date, roundUp) {
if (_.isString(date)) { if (_.isString(date)) {
date = dateMath.parse(date, roundUp); date = dateMath.parse(date, roundUp);
} }
return Math.round(date.valueOf() / 1000); return Math.round(date.valueOf() / 1000);
} };
function convertDimensionFormat(dimensions, scopedVars) { this.convertDimensionFormat = function(dimensions, scopedVars) {
return _.map(dimensions, function(value, key) { return _.map(dimensions, function(value, key) {
return { return {
Name: templateSrv.replace(key, scopedVars), Name: templateSrv.replace(key, scopedVars),
Value: templateSrv.replace(value, scopedVars) Value: templateSrv.replace(value, scopedVars)
}; };
}); });
} };
} }

View File

@ -1 +1,19 @@
<cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter> <cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
<div class="editor-row">
<div class="section">
<h5>Prefix matching</h5>
<div class="editor-option">
<editor-checkbox text="Enable" model="ctrl.annotation.prefixMatching"></editor-checkbox>
</div>
<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
<label class="small">Action</label>
<input type="text" class="input-small" ng-model='ctrl.annotation.actionPrefix'></input>
</div>
<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
<label class="small">Alarm Name</label>
<input type="text" class="input-small" ng-model='ctrl.annotation.alarmNamePrefix'></input>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
import "../datasource";
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
import moment from 'moment';
import helpers from 'test/specs/helpers';
import {CloudWatchDatasource} from "../datasource";
import CloudWatchAnnotationQuery from '../annotation_query';
describe('CloudWatchAnnotationQuery', function() {
var ctx = new helpers.ServiceTestContext();
var instanceSettings = {
jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
};
beforeEach(angularMocks.module('grafana.core'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
ctx.$q = $q;
ctx.$httpBackend = $httpBackend;
ctx.$rootScope = $rootScope;
ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings});
}));
describe('When performing annotationQuery', function() {
var parameter = {
annotation: {
region: 'us-east-1',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678'
},
statistics: ['Average'],
period: 300
},
range: {
from: moment(1443438674760),
to: moment(1443460274760)
}
};
var alarmResponse = {
MetricAlarms: [
{
AlarmName: 'test_alarm_name'
}
]
};
var historyResponse = {
AlarmHistoryItems: [
{
Timestamp: '2015-01-01T00:00:00.000Z',
HistoryItemType: 'StateUpdate',
AlarmName: 'test_alarm_name',
HistoryData: '{}',
HistorySummary: 'test_history_summary'
}
]
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(params) {
switch (params.data.action) {
case 'DescribeAlarmsForMetric':
return ctx.$q.when({data: alarmResponse});
case 'DescribeAlarmHistory':
return ctx.$q.when({data: historyResponse});
}
};
});
it('should return annotation list', function(done) {
var annotationQuery = new CloudWatchAnnotationQuery(ctx.ds, parameter.annotation, ctx.$q, ctx.templateSrv);
annotationQuery.process(parameter.range.from, parameter.range.to).then(function(result) {
expect(result[0].title).to.be('test_alarm_name');
expect(result[0].text).to.be('test_history_summary');
done();
});
ctx.$rootScope.$apply();
});
});
});

View File

@ -187,59 +187,4 @@ describe('CloudWatchDatasource', function() {
expect(scenario.request.data.action).to.be('ListMetrics'); expect(scenario.request.data.action).to.be('ListMetrics');
}); });
}); });
describe('When performing annotationQuery', function() {
var parameter = {
annotation: {
region: 'us-east-1',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678'
},
statistics: ['Average'],
period: 300
},
range: {
from: moment(1443438674760),
to: moment(1443460274760)
}
};
var alarmResponse = {
MetricAlarms: [
{
AlarmName: 'test_alarm_name'
}
]
};
var historyResponse = {
AlarmHistoryItems: [
{
Timestamp: '2015-01-01T00:00:00.000Z',
HistoryItemType: 'StateUpdate',
AlarmName: 'test_alarm_name',
HistoryData: '{}',
HistorySummary: 'test_history_summary'
}
]
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(params) {
switch (params.data.action) {
case 'DescribeAlarmsForMetric':
return ctx.$q.when({data: alarmResponse});
case 'DescribeAlarmHistory':
return ctx.$q.when({data: historyResponse});
}
};
});
it('should return annotation list', function(done) {
ctx.ds.annotationQuery(parameter).then(function(result) {
expect(result[0].title).to.be('test_alarm_name');
expect(result[0].text).to.be('test_history_summary');
done();
});
ctx.$rootScope.$apply();
});
});
}); });

Some files were not shown because too many files have changed in this diff Show More