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
fig.yml
docker-compose.yml
profile.cov
/grafana
.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)
* **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883)
* **Cli**: New cli tool for downloading and updating plugins
* **Annotations**: Annotations can now contain links that can be clicked (you can navigate on to annotation popovers), closes [#1588](https://github.com/grafana/grafana/issues/1588)
### Breaking changes
* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.

View File

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

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/defaults.ini`.
Start Grafana by executing `./grafana-server web`. The `grafana-server` binary needs
the working directory to be the root install directory (where the binary
and the `public` folder is located).
Start Grafana by executing `./bin/grafana-server web`. The `grafana-server`
binary needs the working directory to be the root install directory (where the
binary and the `public` folder is located).

View File

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

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

View File

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

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 {
Text string `json:"text"`
Icon string `json:"icon"`
Img string `json:"img"`
Url string `json:"url"`
Children []*NavLink `json:"children"`
Text string `json:"text,omitempty"`
Icon string `json:"icon,omitempty"`
Img string `json:"img,omitempty"`
Url string `json:"url,omitempty"`
Divider bool `json:"divider,omitempty"`
Children []*NavLink `json:"children,omitempty"`
}

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

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) {
result := http.Header{}
query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId}
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
if err := bus.Dispatch(&query); err != nil {
return nil, err

View File

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

View File

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

View File

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

View File

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

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) {
appSettingsV2 := Table{
Name: "app_settings",
pluginSettingTable := Table{
Name: "plugin_setting",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: true},
{Name: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "enabled", Type: DB_Bool, Nullable: false},
{Name: "pinned", Type: DB_Bool, Nullable: false},
{Name: "json_data", Type: DB_Text, Nullable: true},
@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) {
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id", "app_id"}, Type: UniqueIndex},
{Cols: []string{"org_id", "plugin_id"}, Type: UniqueIndex},
},
}
mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings"))
mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2))
mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable))
//------- indexes ------------------
addTableIndicesMigrations(mg, "v3", appSettingsV2)
addTableIndicesMigrations(mg, "v1", pluginSettingTable)
}

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

View File

@ -38,7 +38,6 @@ export class SideMenuCtrl {
openUserDropdown() {
this.orgMenu = [
{section: 'You', cssClass: 'dropdown-menu-title'},
{text: 'Preferences', url: this.getUrl('/profile')},
{text: 'Profile', url: this.getUrl('/profile')},
];
@ -100,6 +99,22 @@ export function sideMenuDirective() {
bindToController: true,
controllerAs: 'ctrl',
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 {sideMenuDirective} from './components/sidemenu/sidemenu';
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 {arrayJoin} from './directives/array_join';
import 'app/core/controllers/all';
@ -32,4 +34,13 @@ import 'app/core/routes/routes';
import './filters/filters';
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 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) {
var text = sanitizeString(event.text);
@ -42,9 +43,10 @@ function ($, _, coreModule) {
if (tags && tags.length) {
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>";
var $tooltip = $(tooltip);

View File

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

View File

@ -39,7 +39,7 @@ function (angular, coreModule, kbn) {
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
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">' +
attrs.text + tip + '</label>' +
'<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +

View File

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

View File

@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
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');
$routeProvider
@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'SnapshotsCtrl',
controllerAs: 'ctrl',
})
.when('/apps', {
templateUrl: 'public/app/features/apps/partials/list.html',
controller: 'AppListCtrl',
.when('/plugins', {
templateUrl: 'public/app/features/plugins/partials/list.html',
controller: 'PluginListCtrl',
controllerAs: 'ctrl',
resolve: loadAppsBundle,
resolve: loadPluginsBundle,
})
.when('/apps/:appId/edit', {
templateUrl: 'public/app/features/apps/partials/edit.html',
controller: 'AppEditCtrl',
.when('/plugins/:pluginId/edit', {
templateUrl: 'public/app/features/plugins/partials/edit.html',
controller: 'PluginEditCtrl',
controllerAs: 'ctrl',
resolve: loadAppsBundle,
resolve: loadPluginsBundle,
})
.when('/apps/:appId/page/:slug', {
templateUrl: 'public/app/features/apps/partials/page.html',
.when('/plugins/:pluginId/page/:slug', {
templateUrl: 'public/app/features/plugins/partials/page.html',
controller: 'AppPageCtrl',
controllerAs: 'ctrl',
resolve: loadAppsBundle,
resolve: loadPluginsBundle,
})
.when('/global-alerts', {
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) {
switch (true) {
// 0.5s
case (interval <= 500):
// 0.3s
case (interval <= 300):
return 100; // 0.1s
// 5s
case (interval <= 5000):
// 0.75s
case (interval <= 750):
return 500; // 0.5s
// 1.5s
case (interval <= 1500):
return 1000; // 1s
// 3.5s
case (interval <= 3500):
return 2000; // 2s
// 7.5s
case (interval <= 7500):
return 5000; // 5s
// 15s
case (interval <= 15000):
// 12.5s
case (interval <= 12500):
return 10000; // 10s
// 17.5s
case (interval <= 17500):
return 15000; // 15s
// 25s
case (interval <= 25000):
return 20000; // 20s
// 45s
case (interval <= 45000):
return 30000; // 30s
// 3m
case (interval <= 180000):
// 1.5m
case (interval <= 90000):
return 60000; // 1m
// 9m
// 3.5m
case (interval <= 210000):
return 120000; // 2m
// 7.5m
case (interval <= 450000):
return 300000; // 5m
// 20m
case (interval <= 1200000):
// 12.5m
case (interval <= 750000):
return 600000; // 10m
// 12.5m
case (interval <= 1050000):
return 900000; // 15m
// 25m
case (interval <= 1500000):
return 1200000; // 20m
// 45m
case (interval <= 2700000):
return 1800000; // 30m
// 2h
case (interval <= 7200000):
// 1.5h
case (interval <= 5400000):
return 3600000; // 1h
// 6h
case (interval <= 21600000):
// 2.5h
case (interval <= 9000000):
return 7200000; // 2h
// 4.5h
case (interval <= 16200000):
return 10800000; // 3h
// 9h
case (interval <= 32400000):
return 21600000; // 6h
// 24h
case (interval <= 86400000):
return 43200000; // 12h
@ -593,12 +620,12 @@ function($, _) {
{text: 'packets/sec', value: 'pps'},
{text: 'bits/sec', value: 'bps'},
{text: 'bytes/sec', value: 'Bps'},
{text: 'kilobites/sec', value: 'Kbits'},
{text: 'kilobits/sec', value: 'Kbits'},
{text: 'kilobytes/sec', value: 'KBs'},
{text: 'megabites/sec', value: 'Mbits'},
{text: 'megabits/sec', value: 'Mbits'},
{text: 'megabytes/sec', value: 'MBs'},
{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>
<div class="page-container">

View File

@ -1,38 +1,29 @@
<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>
<div class="page-container">
<div class="page-header">
<h1>
Edit Organization
</h1>
<h1>Edit Organization</h1>
</div>
<form name="orgDetailsForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
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>
<form name="orgDetailsForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="org.name" class="gf-form-input max-width-14" >
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
</div>
</form>
<h3>
Organization Users
</h3>
<h3 class="page-heading">Organization Users</h3>
<table class="grafana-options-table form-inline">
<table class="grafana-options-table">
<tr>
<th>Username</th>
<th>Email</th>
@ -43,7 +34,7 @@
<td>{{orgUser.login}}</td>
<td>{{orgUser.email}}</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>
</td>
<td style="width: 1%">
@ -53,6 +44,4 @@
</td>
</tr>
</table>
</div>
</div>

View File

@ -1,129 +1,78 @@
<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>
<div class="page-container">
<div class="page-header">
<h1>
Edit User
</h1>
<h1>Edit User</h1>
</div>
<form name="userForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<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>
<form name="userForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="user.name" class="gf-form-input max-width-25" >
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
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 class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Username
</li>
<li>
<input type="text" ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form">
<span class="gf-form-label width-10">Email</span>
<input type="email" ng-model="user.email" class="gf-form-input max-width-25" >
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Username</span>
<input type="text" ng-model="user.login" class="gf-form-input max-width-25" >
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
</form>
<h3>
Change password
</h3>
<form name="passwordForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<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>
<br>
<button type="submit" class="pull-right 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 class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
</div>
</form>
<table class="grafana-options-table form-inline">
<h3 class="page-heading">Change password</h3>
<form name="passwordForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">New password</span>
<input type="password" required ng-minlength="4" ng-model="password" class="gf-form-input max-width-25">
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="setPassword()">Update</button>
</div>
</form>
<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>
<th>Name</th>
<th>Role</th>
@ -134,7 +83,7 @@
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
</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>
</td>
<td style="width: 1%">

View File

@ -1,63 +1,35 @@
<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>
<div class="page-container">
<div class="page-header">
<h1>
Add new user
</h1>
<h1>Add new user</h1>
</div>
<form name="userForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
<strong>Name</strong>
</li>
<li>
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
<form name="userForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="user.name" class="gf-form-input max-width-20" >
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
<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 class="gf-form">
<span class="gf-form-label width-10">Email</span>
<input type="email" ng-model="user.email" class="gf-form-input max-width-20" >
</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 class="gf-form">
<span class="gf-form-label width-10">Username</span>
<input type="text" ng-model="user.login" class="gf-form-input max-width-20" >
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Password</span>
<input type="password" required ng-model="user.password" class="gf-form-input max-width-20" >
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="create()">Create</button>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="create()">Create</button>
</div>
</form>
</div>

View File

@ -1,12 +1,13 @@
<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>
<div class="page-container">
<div class="page-header">
<h1>
Organizations
</h1>
<h1>Organizations</h1>
</div>
<table class="filter-table form-inline">

View File

@ -3,9 +3,7 @@
<div class="page-container">
<div class="page-header">
<h1>
Server settings
</h1>
<h1>Server settings</h1>
</div>
<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-header">
<h1>
Stats
</h1>
<h1>Stats</h1>
</div>
<table class="filter-table form-inline">

View File

@ -1,5 +1,8 @@
<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>
<div class="page-container">

View File

@ -1,43 +1,32 @@
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-bolt"></i>
<div class="tabbed-view-header">
<h2 class="tabbed-view-title">
Annotations
</div>
</h2>
<div class="tabs">
<ul class="nav nav-tabs">
<li ng-class="{active: mode === 'list'}">
<a ng-click="mode = 'list';">
<ul class="gf-tabs">
<li class="gf-tabs-item" >
<a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
List
</a>
</li>
<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'">
<a>
<li class="gf-tabs-item" ng-show="mode === 'edit'">
<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
{{currentAnnotation.name}}
</a>
</li>
<li ng-class="{active: mode === 'new'}">
<a ng-click="mode = 'new';">
<i class="fa fa-plus"></i>
New
</a>
<li class="gf-tabs-item" ng-show="mode === 'new'">
<span class="active gf-tabs-link">New</span>
</li>
</ul>
</div>
<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>
</button>
</div>
<div class="gf-box-body">
<div class="tabbed-view-body">
<div class="editor-row row" ng-if="mode === 'list'">
<div class="span6">
<div ng-if="annotations.length === 0">
<em>No annotations defined</em>
</div>
@ -50,13 +39,13 @@
<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%" class="nobg">
<td style="width: 1%">
<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
<td style="width: 1%" class="nobg">
<td style="width: 1%">
<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
@ -64,43 +53,47 @@
</tr>
</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 ng-if="mode === 'edit' || mode === 'new'">
<div class="editor-row">
<div class="editor-option">
<label class="small">Name</label>
<input type="text" class="input-medium" ng-model='currentAnnotation.name' placeholder="name"></input>
<div class="annotations-basic-settings" ng-if="mode === 'edit' || mode === 'new'">
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form gf-size-max-xxl">
<span class="gf-form-label">Name</span>
<input type="text" class="gf-form-input" ng-model='currentAnnotation.name' placeholder="name"></input>
</div>
<div class="editor-option">
<label class="small">Datasource</label>
<select ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
<div class="gf-form">
<span class="gf-form-label max-width-10">Datasource</span>
<div class="gf-form-select-wrapper">
<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>
</div>
<div class="editor-option text-center">
<label class="small">Icon color</label>
</div>
<div class="gf-form">
<label class="gf-form-label">
<span>Color</span>
<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
</label>
</div>
<div class="editor-option">
<label class="small">Icon size</label>
<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>
</div>
<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
<div class="editor-option text-center">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
</div>
</div>
<rebuild-on-change property="currentAnnotation.datasource">
<rebuild-on-change property="currentDatasource">
<plugin-component type="annotations-query-ctrl">
</plugin-component>
</rebuild-on-change>
<br>
<button ng-show="mode === 'new'" type="button" class="btn btn-success" ng-click="add()">Add</button>
<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
<br>
<br>
<div class="gf-form">
<div class="gf-form-button-row p-y-0">
<button ng-show="mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="add()">Add</button>
<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update()">Update</button>
</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">
<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">
<li ng-if="dashboardMeta.canEdit">
<li>
<a class="pointer" ng-click="shareDashboard(0)">
<i class="fa fa-link"></i> Link to Dashboard
</a>
</li>
<li ng-if="dashboardMeta.canEdit">
<li>
<a class="pointer" ng-click="shareDashboard(1)">
<i class="icon-gf icon-gf-snapshot"></i>Snapshot sharing
</a>

View File

@ -12,6 +12,8 @@ export class DashNavCtrl {
$scope.init = function() {
$scope.onAppEvent('save-dashboard', $scope.saveDashboard);
$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;
@ -52,6 +54,10 @@ export class DashNavCtrl {
});
};
$scope.quickSnapshot = function() {
$scope.shareDashboard(1);
};
$scope.openSearch = function() {
$scope.appEvent('show-dash-search');
};

View File

@ -60,6 +60,14 @@ function(angular, $) {
scope.appEvent('zoom-out', evt);
}, { 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() {
var popups = $('.popover.in');
if (popups.length > 0) {

View File

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

View File

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

View File

@ -35,53 +35,30 @@
<div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form">
<div class="gf-form-row">
<span class="gf-fluid-input">
<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea>
</span>
<div class="gf-form-group position-center">
<div class="gf-form width-30" >
<textarea rows="5" data-share-panel-url class="gf-form-input width-30" ng-model='iframeHtml'></textarea>
</div>
</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>
</script>
<script type="text/ng-template" id="shareLinkOptions.html">
<div class="editor-row" style="margin: 11px 20px 33px 20px">
<div class="section">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px;">
<label class="checkbox-label" for="options.forCurrent">Current time range</label>
</li>
<li class="tight-form-item last">
<input class="cr1" id="options.forCurrent" type="checkbox" ng-model="options.forCurrent" ng-checked="options.forCurrent" ng-change="buildUrl()">
<label for="options.forCurrent" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form-group position-center">
<div class="gf-form">
<editor-checkbox text="Current time range" model="options.forCurrent" change="updated()"></editor-checkbox>
</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 class="gf-form">
<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="updated()"></editor-checkbox>
</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 class="gf-form">
<span class="gf-form-label">Theme</span>
<div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
</div>
</div>
</div>
@ -93,14 +70,19 @@
</div>
<div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form">
<div class="gf-form-row">
<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">
<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
</span>
<div class="gf-form-group position-center">
<div class="gf-form-inline">
<div class="gf-form width-30">
<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
</div>
<div class="editor-row" style="margin-top: 5px;" ng-show="modeSharePanel">
<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="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>
</div>
</div>
@ -132,29 +114,15 @@
</p>
</div>
<div class="editor-row share-modal-options" style="">
<div class="section" ng-if="step === 1">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px;">
Snapshot name
</li>
<li>
<input type="text" ng-model="snapshot.name" class="input-large tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form-group share-modal-options position-center">
<div class="gf-form" ng-if="step === 1">
<span class="gf-form-label width-12">Snapshot name</span>
<input type="text" ng-model="snapshot.name" class="gf-form-input max-width-15" >
</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 class="gf-form" ng-if="step === 1">
<span class="gf-form-label width-12">Expire</span>
<div class="gf-form-select-wrapper max-width-15">
<select class="gf-form-input" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
</div>
</div>
@ -168,9 +136,8 @@
<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 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">
<i class="fa fa-save"></i>
Local Snapshot
@ -184,6 +151,7 @@
<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>
</div>
</div>
</div>
</script>

View File

@ -1,16 +1,14 @@
<div class="submenu-controls">
<div class="tight-form borderless">
<ul class="tight-form-list" ng-if="ctrl.dashboard.templating.list.length > 0">
<ul ng-if="ctrl.dashboard.templating.list.length > 0">
<li ng-repeat="variable in ctrl.variables" class="submenu-item">
<span class="template-variable tight-form-item" ng-show="!variable.hideLabel" style="padding-right: 5px">
<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.annotations.list.length > 0">
<ul ng-if="ctrl.dashboard.annotations.list.length > 0">
<li ng-repeat="annotation in ctrl.dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
<a ng-click="ctrl.disableAnnotation(annotation)">
<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
@ -21,10 +19,9 @@
</li>
</ul>
<ul class="tight-form-list pull-right" ng-if="ctrl.dashboard.links.length > 0">
<ul class="pull-right" ng-if="ctrl.dashboard.links.length > 0">
<dash-links-container links="ctrl.dashboard.links"></dash-links-container>
</ul>
<div class="clearfix"></div>
</div>
</div>

View File

@ -1,48 +1,15 @@
<div class="editor-row">
<div class="section">
<div>
<!-- <div class="tight&#45;form"> -->
<!-- <ul class="tight&#45;form&#45;list"> -->
<!-- <li class="tight&#45;form&#45;item" style="width: 118px"> -->
<!-- Relative times -->
<!-- </li> -->
<!-- <li> -->
<!-- <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> -->
<!-- </li> -->
<!-- </ul> -->
<!-- <div class="clearfix"></div> -->
<!-- </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 class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Auto-refresh</span>
<input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.refresh_intervals" array-join>
</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"
<div class="gf-form">
<span class="gf-form-label width-10">Now delay now-</span>
<input type="text" class="gf-form-input max-width-25"
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>

View File

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

View File

@ -1,90 +1,76 @@
<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 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>
<div ng-repeat="link in dashboard.links">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 20px">
<i class="fa fa-fw fa-unlink"></i>
</li>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<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>
<li>
<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>
</li>
<div class="gf-form" ng-show="link.type === 'dashboards'">
<span class="gf-form-label">With tags</span>
<bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags"></bootstrap-tagsinput>
</div>
<li class="tight-form-item" ng-show="link.type === 'dashboards'">With tags</li>
<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'">
<div class="gf-form" ng-show="link.type === 'dashboards'">
<editor-checkbox text="As dropdown" model="link.asDropdown" change="updated()"></editor-checkbox>
</li>
<li class="tight-form-item" ng-show="link.type === 'dashboards' && link.asDropdown">
Title
</li>
<li ng-show="link.type === 'dashboards' && link.asDropdown">
<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">Url</li>
<li ng-show="link.type === 'link'">
<input type="text" ng-model="link.url" class="input-xlarge tight-form-input" style="width: 302px;" ng-model-onblur ng-change="updated()">
</li>
<li class="tight-form-item" ng-show="link.type === 'link'">
</div>
<div class="gf-form max-width-30" ng-show="link.type === 'link'">
<li class="gf-form-label width-6">Url</li>
<input type="text" ng-model="link.url" class="gf-form-input" ng-model-onblur ng-change="updated()">
</div>
<div class="gf-form">
<button class="btn btn-inverse btn-mini" ng-click="moveLink($index, -1)" ng-hide="$first"><i class="fa fa-arrow-up"></i></button>
</div>
<div class="gf-form">
<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>
</li>
</ul>
<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 class="editor-row">
<br>
<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
</div>
</div>
</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">
<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>
<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>
</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>
</div>
</div>
</div>
<rebuild-on-change property="datasourceMeta.id">

View File

@ -6,14 +6,28 @@
<div class="gf-form">
<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>
<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 class="gf-form">
<span class="gf-form-label width-7">
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip>
</span>
<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 class="gf-form">
<span class="gf-form-label width-7">

View File

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

View File

@ -10,23 +10,16 @@
</button>
</div>
<div class="gf-box-body" style="min-height: 0px;">
<div class="gf-box-body">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item">
<strong>Key</strong>
</li>
<li class="tight-form-item last">
{{key}}
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">Key</span>
<span class="gf-form-label">{{key}}</span>
</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.
<br>
<br>

View File

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

View File

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

View File

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

View File

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

View File

@ -52,6 +52,5 @@ function (angular, _) {
$scope.deleteLink = function(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>
<div class="page-container" ng-form="playlistEditForm">
@ -7,6 +7,8 @@
<h1 ng-show="!ctrl.isNew()">Edit Playlist</h1>
</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">
<span class="gf-form-label width-7">Name</span>
@ -19,24 +21,29 @@
</div>
<div class="gf-form-group">
<div class="max-width-28">
<h5 class="page-headering">Add dashboards</h5>
<h3 class="page-headering">Dashboards</h3>
</div>
<div class="row">
<div class="col-md-6">
<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 class="row">
<div class="col-md-6">
<h5>Search results ({{ctrl.filteredDashboards.length + ctrl.filteredTags.length}})</h5>
<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">
<td style="white-space: nowrap;">
{{playlistItem.title}}
<td>
<i class="icon-gf icon-gf-dashboard"></i>
&nbsp;&nbsp;{{playlistItem.title}}
<i class="fa fa-star" ng-show="playlistItem.isStarred"></i>
</td>
<td style="text-align: center">
<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
@ -46,31 +53,40 @@
</table>
</div>
<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;"
ng-click="ctrl.addTagPlaylistItem(tag, $event)">
<table class="grafana-options-table playlist-available-list">
<tr ng-repeat="tag in ctrl.filteredTags">
<td>
<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
<i class="fa fa-tag"></i>
<span>{{tag.term}} &nbsp;({{tag.count}})</span>
</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 class="col-md-6">
<h5>Added dashboards</h5>
<table class="grafana-options-table">
<h5 class="page headering playlist-column-header">Selected</h5>
<table class="grafana-options-table playlist-available-list">
<tr ng-repeat="playlistItem in ctrl.playlistItems">
<td style="white-space: nowrap;" ng-if="playlistItem.type === 'dashboard_by_id'">
{{playlistItem.title}}
<td ng-if="playlistItem.type === 'dashboard_by_id'">
<i class="icon-gf icon-gf-dashboard"></i>&nbsp;&nbsp;{{playlistItem.title}}
</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">
<i class="fa fa-tag"></i>
<span>{{playlistItem.title}}</span>
</a>
</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)">
<i class="fa fa-arrow-up"></i>
</button>
@ -89,7 +105,10 @@
<div class="clearfix"></div>
<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-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</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>
<div class="page-container">

View File

@ -11,7 +11,7 @@ export class PlaylistEditCtrl {
searchQuery: string = '';
loading: boolean = false;
playlist: any = {
interval: '10m',
interval: '5m',
};
playlistItems: 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>
<div class="page-container">

View File

@ -1,10 +1,11 @@
<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots">
<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots" title-url="dashboard/snapshots">
</navbar>
<div class="page-container">
<div class="page-wide">
<div class="page-header">
<h1>Available snapshots</h1>
</div>
<table class="filter-table" style="margin-top: 20px">
<thead>
@ -36,5 +37,4 @@
</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>
<div class="page-container">

View File

@ -1,45 +1,33 @@
<div ng-controller="TemplateEditorCtrl" ng-init="init()">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-code"></i>
<div class="tabbed-view-header">
<h2 class="tabbed-view-title">
Templating
</div>
</h2>
<div class="tabs">
<ul class="nav nav-tabs">
<li ng-class="{active: mode === 'list'}">
<a ng-click="mode = 'list';">
<ul class="gf-tabs">
<li class="gf-tabs-item" >
<a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
Variables
</a>
</li>
<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'">
<a>
<li class="gf-tabs-item" ng-show="mode === 'edit'">
<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
{{current.name}}
</a>
</li>
<li ng-class="{active: mode === 'new'}">
<a ng-click="mode = 'new';">
<i class="fa fa-plus"></i>
New
</a>
<li class="gf-tabs-item" ng-show="mode === 'new'">
<span class="active gf-tabs-link">New</span>
</li>
</ul>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
<button class="tabbed-view-close-btn" ng-click="dismiss();dashboard.refresh();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="gf-box-body">
<div class="tabbed-view-body">
<div ng-if="mode === 'list'">
<div class="editor-row row">
<div style="max-width: 1024px">
<div ng-if="variables.length === 0">
<em>No template variables defined</em>
</div>
@ -53,280 +41,178 @@
<td class="max-width" style="max-width: 200px;">
{{variable.query}}
</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="edit(variable)" class="btn btn-inverse btn-small">
<a ng-click="duplicate(variable)" class="btn btn-inverse btn-mini">
Duplicate
</a>
</td>
<td style="width: 1%">
<a ng-click="edit(variable)" class="btn btn-inverse btn-mini">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
<td style="width: 1%">
<a ng-click="duplicate(variable)" class="btn btn-inverse btn-small">
Duplicate
</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">
<a ng-click="removeVariable(variable)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</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 ng-if="mode === 'edit' || mode === 'new'">
<div class="editor-row">
<div class="tight-form-section">
<h5>Variable</h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Name
</li>
<li>
<input type="text" class="input-large tight-form-input" placeholder="name" ng-model='current.name'></input>
</li>
<li class="tight-form-item">
Type
</li>
<li>
<select class="input-small tight-form-input" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
</li>
<li class="tight-form-item" ng-show="current.type === 'query'">
Data source
</li>
<li ng-show="current.type === 'query'">
<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>
</li>
</ul>
<div class="clearfix"></div>
<h5 class="section-heading">Variable</h5>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-7">Name</span>
<input type="text" class="gf-form-input max-width-14" placeholder="name" ng-model='current.name'></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Type</span>
<div class="gf-form-select-wrapper max-width-10">
<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>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span>
<div class="gf-form-select-wrapper" ng-show="current.type === 'query'">
<select class="gf-form-input max-width-14" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
</div>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Label</span>
<input type="text" class="gf-form-input max-width-14" ng-model='current.label' placeholder="optional display name"></input>
<editor-checkbox class="width-13" text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
</div>
</div>
<div class="editor-row">
<div class="tight-form-section">
<h5>Value Options</h5>
<div ng-show="current.type === 'interval'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
Values
</li>
<li>
<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>
</li>
</ul>
<div class="clearfix"></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="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
<div class="gf-form">
<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox>
</li>
<li class="tight-form-item" ng-show="current.auto">
<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>
</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">
</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>
</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>
</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'">
<div class="tight-form last">
<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 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 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 ng-show="current.type === 'query'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Query
</li>
<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 ng-show="current.type === 'query'" class="gf-form-group">
<div class="gf-form">
<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>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
<div class="gf-form">
<span class="gf-form-label width-7">
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>
</span>
<input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
</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 class="gf-form">
<span class="gf-form-label width-7">All value</span>
<editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
</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>
<div class="gf-form-inline" ng-show="current.includeAll">
<div class="gf-form">
<span class="gf-form-label width-7">All format</span>
<div class="gf-form-select-wrapper">
<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 class="gf-form max-width-30">
<span class="gf-form-label width-7">All value</span>
<input type="text" class="gf-form-input" ng-model='current.options[0].value'></input>
</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>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="editor-row">
<div class="tight-form-section" ng-hide="current.type === 'interval'">
<h5>Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item last" style="width: 100px;">
<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="gf-form">
<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 class="tight-form-section">
<h5>Display options</h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Variable Label
</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>
<span class="gf-form-label" ng-show="current.multi">Multi format</span>
<div class="gf-form-select-wrapper max-width-10" ng-show="current.multi">
<select class="gf-form-input" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
</div>
</div>
</div>
<div class="editor-row" ng-if="current.type === 'query'">
<div class="tight-form-section">
<div class="gf-form-group" ng-if="current.type === 'query'">
<h5>Value groups/tags (Experimental feature)</h5>
<div class="tight-form last" ng-if="current.useTags">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 135px">
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">
<div class="gf-form">
<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="gf-form last" ng-if="current.useTags">
<span class="gf-form-label width-10">Tags query</span>
<input type="text" class="gf-form-input" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
</div>
<div class="gf-form" ng-if="current.useTags">
<li class="gf-form-label width-10">Tag values query</li>
<input type="text" class="gf-form-input" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
</div>
</div>
<div class="editor-row">
<div class="tight-form-section">
<div class="gf-form-group">
<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">
<div class="gf-form">
<span class="gf-form-label" ng-repeat="option in current.options | limitTo: 20">
{{option.text}}
</li>
</ul>
<div class="clearfix"></div>
</div>
</span>
</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 === 'new'" ng-click="add();">Add</button>
</div>
</div>
</div>

View File

@ -39,4 +39,12 @@ declare module 'app/core/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>
<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-pane"
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 class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-edit"></i>
<div class="tabbed-view-header">
<h2 class="tabbed-view-title">
JSON
</div>
</h2>
<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>
</button>
</div>
<div class="gf-box-body" style="height: 500px">
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 100%;"></textarea>
<br>
<br>
<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
<div class="tabbed-view-body">
<div class="gf-form">
<textarea class="gf-form-input" ng-model="json" rows="20" spellcheck="false"></textarea>
</div>
<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>

View File

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

View File

@ -8,7 +8,7 @@
<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>
</div>
<div class="gf-form max-width-21">
<div class="gf-form max-width-26">
<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>
<editor-checkbox text="Transparent" model="ctrl.panel.transparent"></editor-checkbox>
@ -31,10 +31,6 @@
</div>
</div>
<br>
<br>
<br>
<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-logo">
<a href="login">
<img src="img/logo_transparent_200x75.png">
</a>
<img class="logo-icon" src="public/img/grafana_icon.svg"></img><br>
<i class="icon-gf icon-gf-grafana_wordmark"></i>
</div>
<div class="login-inner-box">
@ -15,76 +14,50 @@
</button>
</div>
<form name="sendResetForm" class="login-form" ng-show="mode === 'send'">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 78px">
<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>
<form name="sendResetForm" class="login-form gf-form-group" ng-show="mode === 'send'">
<div class="gf-form">
<span class="gf-form-label width-7">User</span>
<input type="text" name="username" class="gf-form-input max-width-14" required ng-model='formModel.userOrEmail' placeholder="email or username">
</div>
<div class="login-submit-button-row">
<button type="submit" class="btn" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}">
<div class="gf-form-button-row">
<button type="submit" class="btn btn-large" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}">
Send reset instructions
</button>
</div>
</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.
</h5>
<form name="resetForm" class="login-form" ng-show="mode === 'reset'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 125px">
<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>
<form name="resetForm" class="login-form gf-form-group" ng-show="mode === 'reset'">
<div class="gf-form">
<span class="gf-form-label width-10">New Password</span>
<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;">
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 125px">
<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 class="gf-form">
<span class="gf-form-label width-10">Confirm Password</span>
<input type="password" name="ConfirmPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password">
</div>
<div style="margin-left: 141px; width: 207px;">
<password-strength password="formModel.newPassword"></password-strength>
</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}">
Reset Password
</button>
</div>
</form>
<div class="clearfix"></div>
</div>
<div class="row" style="margin-top: 40px">
<div class="row" style="margin-top: 20px">
<div class="text-center">
<a href="login">
Back to login
</a>
<a href="login">Back to login</a>
</div>
</div>
</div>
</div>

View File

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

View File

@ -1,5 +1,5 @@
<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}}
<span ng-repeat="tag in vm.selectedTags" bs-tooltip='tag.valuesText' data-placement="bottom">
<span class="label-tag"tag-color-from-name="tag.text">
@ -10,7 +10,7 @@
<i class="fa fa-caret-down"></i>
</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-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',
'moment',
'app/core/utils/datemath',
'./annotation_query',
],
function (angular, _, moment, dateMath) {
function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
'use strict';
/** @ngInject */
@ -15,9 +16,10 @@ function (angular, _, moment, dateMath) {
this.proxyUrl = instanceSettings.url;
this.defaultRegion = instanceSettings.jsonData.defaultRegion;
var self = this;
this.query = function(options) {
var start = convertToCloudWatchTime(options.range.from, false);
var end = convertToCloudWatchTime(options.range.to, true);
var start = self.convertToCloudWatchTime(options.range.from, false);
var end = self.convertToCloudWatchTime(options.range.to, true);
var queries = [];
options = angular.copy(options);
@ -30,7 +32,7 @@ function (angular, _, moment, dateMath) {
query.region = templateSrv.replace(target.region, options.scopedVars);
query.namespace = templateSrv.replace(target.namespace, 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;
var range = end - start;
@ -117,7 +119,7 @@ function (angular, _, moment, dateMath) {
parameters: {
namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName),
dimensions: convertDimensionFormat(filterDimensions, {}),
dimensions: this.convertDimensionFormat(filterDimensions, {}),
}
};
@ -206,6 +208,14 @@ function (angular, _, moment, dateMath) {
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) {
return this.awsRequest({
region: region,
@ -223,55 +233,8 @@ function (angular, _, moment, dateMath) {
};
this.annotationQuery = function(options) {
var annotation = options.annotation;
var region = templateSrv.replace(annotation.region);
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;
var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
return annotationQuery.process(options.range.from, options.range.to);
};
this.testDatasource = function() {
@ -347,21 +310,21 @@ function (angular, _, moment, dateMath) {
});
}
function convertToCloudWatchTime(date, roundUp) {
this.convertToCloudWatchTime = function(date, roundUp) {
if (_.isString(date)) {
date = dateMath.parse(date, roundUp);
}
return Math.round(date.valueOf() / 1000);
}
};
function convertDimensionFormat(dimensions, scopedVars) {
this.convertDimensionFormat = function(dimensions, scopedVars) {
return _.map(dimensions, function(value, key) {
return {
Name: templateSrv.replace(key, scopedVars),
Value: templateSrv.replace(value, scopedVars)
};
});
}
};
}

View File

@ -1 +1,19 @@
<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');
});
});
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