mirror of
https://github.com/grafana/grafana.git
synced 2025-01-26 08:16:59 -06:00
Merge branch 'master' into docs-2.0
This commit is contained in:
commit
75e6947c5a
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,4 +1,24 @@
|
||||
# 2.0.0 (2015-04-20)
|
||||
# 2.1.0 (unreleased - master branch)
|
||||
|
||||
**Backend**
|
||||
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
|
||||
|
||||
|
||||
# 2.0.3 (unreleased - 2.0.x branch)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed
|
||||
- [Issue #1857](https://github.com/grafana/grafana/issues/1857). /api/login/ping Fix for issue when behind reverse proxy and subpath
|
||||
- [Issue #1863](https://github.com/grafana/grafana/issues/1863). MySQL: Dashboard.data column type changed to mediumtext (sql migration added)
|
||||
|
||||
# 2.0.2 (2015-04-22)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1832](https://github.com/grafana/grafana/issues/1832). Graph Panel + Legend Table mode: Many series casued zero height graph, now legend will never reduce the height of the graph below 50% of row height.
|
||||
- [Issue #1846](https://github.com/grafana/grafana/issues/1846). Snapshots: Fixed issue with snapshoting dashboards with an interval template variable
|
||||
- [Issue #1848](https://github.com/grafana/grafana/issues/1848). Panel timeshift: You can now use panel timeshift without a relative time override
|
||||
|
||||
# 2.0.1 (2015-04-20)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1784](https://github.com/grafana/grafana/issues/1784). Data source proxy: Fixed issue with using data source proxy when grafana is behind nginx suburl
|
||||
|
18
Godeps/Godeps.json
generated
18
Godeps/Godeps.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"ImportPath": "github.com/grafana/grafana",
|
||||
"GoVersion": "go1.4.2",
|
||||
"GoVersion": "go1.3",
|
||||
"Packages": [
|
||||
"./pkg/..."
|
||||
],
|
||||
@ -13,6 +13,14 @@
|
||||
"ImportPath": "github.com/Unknwon/macaron",
|
||||
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dalu/slug",
|
||||
"Rev": "6dbd13912e9be466e2c1de349a2c7d1466c97e07"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dalu/unidecode",
|
||||
"Rev": "339814d47f3e32a6f7036a0a4c56ed9b373dd755"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.2-26-g9543750",
|
||||
@ -27,10 +35,6 @@
|
||||
"Comment": "v0.4.2-58-ge2889e5",
|
||||
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gosimple/slug",
|
||||
"Rev": "a2392a4a87fa0366cbff131d3fd421f83f52492f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtolds/gls",
|
||||
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
|
||||
@ -87,10 +91,6 @@
|
||||
{
|
||||
"ImportPath": "gopkgs.com/pool.v1",
|
||||
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/unidecode.v1",
|
||||
"Rev": "4deae2c05236b41cc39f8144ac87a837ba974d40"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ slug
|
||||
Package `slug` generate slug from unicode string, URL-friendly slugify with
|
||||
multiple languages support.
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/gosimple/slug?status.png)](https://godoc.org/github.com/gosimple/slug)
|
||||
[![Build Status](https://drone.io/github.com/gosimple/slug/status.png)](https://drone.io/github.com/gosimple/slug/latest)
|
||||
[![GoDoc](https://godoc.org/github.com/dalu/slug?status.png)](https://godoc.org/github.com/dalu/slug)
|
||||
|
||||
[Documentation online](http://godoc.org/github.com/gosimple/slug)
|
||||
[Documentation online](http://godoc.org/github.com/dalu/slug)
|
||||
|
||||
## Example
|
||||
|
||||
@ -38,12 +37,9 @@ multiple languages support.
|
||||
fmt.Println(textSub) // Will print 'sand-is-hot'
|
||||
}
|
||||
|
||||
### Requests or bugs?
|
||||
<https://github.com/gosimple/slug/issues>
|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/gosimple/slug
|
||||
go get -u github.com/dalu/slug
|
||||
|
||||
## License
|
||||
|
@ -12,7 +12,7 @@ Example:
|
||||
package main
|
||||
|
||||
import(
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/dalu/slug"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@ -35,9 +35,5 @@ Example:
|
||||
textSub := slug.Make("water is hot")
|
||||
fmt.Println(textSub) // Will print 'sand-is-hot'
|
||||
}
|
||||
|
||||
Requests or bugs?
|
||||
|
||||
https://github.com/gosimple/slug/issues
|
||||
*/
|
||||
package slug
|
@ -6,7 +6,7 @@
|
||||
package slug
|
||||
|
||||
import (
|
||||
"gopkgs.com/unidecode.v1"
|
||||
"github.com/dalu/unidecode"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
@ -3,10 +3,4 @@ unidecode
|
||||
|
||||
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
|
||||
|
||||
Please, use the following import path to ensure a stable API:
|
||||
|
||||
```go
|
||||
import "gopkgs.com/unidecode.v1"
|
||||
```
|
||||
|
||||
View other available versions, documentation and examples at http://gopkgs.com/unidecode
|
2
Godeps/_workspace/src/github.com/gosimple/slug/.gitignore
generated
vendored
2
Godeps/_workspace/src/github.com/gosimple/slug/.gitignore
generated
vendored
@ -1,2 +0,0 @@
|
||||
_*
|
||||
cover*.out
|
24
Godeps/_workspace/src/gopkgs.com/unidecode.v1/gopkgs.go
generated
vendored
24
Godeps/_workspace/src/gopkgs.com/unidecode.v1/gopkgs.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
package unidecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// gopkgs.go: v1
|
||||
|
||||
// NOTE: This file is autogenerated by gopkgs.com.
|
||||
const (
|
||||
goPkgsSrcPath = "github.com/rainycape/unidecode"
|
||||
goPkgsName = "unidecode"
|
||||
goPkgsErrFmt = "invalid import path %s - please use gopkgs.com/%s.v1 or see http://gopkgs.com/%s"
|
||||
)
|
||||
|
||||
type goPkgsCheck struct{}
|
||||
|
||||
func init() {
|
||||
typ := reflect.TypeOf(goPkgsCheck{})
|
||||
if typ.PkgPath() == goPkgsSrcPath {
|
||||
panic(fmt.Errorf(goPkgsErrFmt, typ.PkgPath(), goPkgsName, goPkgsName))
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
/* jshint node:true */
|
||||
'use strict';
|
||||
module.exports = function (grunt) {
|
||||
|
||||
var os = require('os');
|
||||
var config = {
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
@ -13,6 +12,10 @@ module.exports = function (grunt) {
|
||||
platform: process.platform.replace('win32', 'windows'),
|
||||
};
|
||||
|
||||
if (process.platform.match(/^win/)) {
|
||||
config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86';
|
||||
}
|
||||
|
||||
config.pkg.version = grunt.option('pkgVer') || config.pkg.version;
|
||||
|
||||
// load plugins
|
||||
@ -35,7 +38,6 @@ module.exports = function (grunt) {
|
||||
|
||||
// Merge that object with what with whatever we have here
|
||||
loadConfig(config,'./tasks/options/');
|
||||
|
||||
// pass the config to grunt
|
||||
grunt.initConfig(config);
|
||||
};
|
||||
|
@ -111,7 +111,7 @@ bra run
|
||||
|
||||
### Running
|
||||
```
|
||||
./grafana web
|
||||
./grafana
|
||||
```
|
||||
|
||||
Open grafana in your browser (default http://localhost:3000) and login with admin user (default user/pass = admin/admin).
|
||||
|
38
build.go
38
build.go
@ -22,13 +22,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
version string = "v1"
|
||||
race bool
|
||||
workingDir string
|
||||
serverBinaryName string = "grafana-server"
|
||||
versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
version string = "v1"
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
linuxPackageVersion string = "v1"
|
||||
linuxPackageIteration string = ""
|
||||
race bool
|
||||
workingDir string
|
||||
serverBinaryName string = "grafana-server"
|
||||
)
|
||||
|
||||
const minGoVersion = 1.3
|
||||
@ -40,7 +43,7 @@ func main() {
|
||||
ensureGoPath()
|
||||
readVersionFromPackageJson()
|
||||
|
||||
log.Printf("Version: %s\n", version)
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration)
|
||||
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
@ -70,7 +73,7 @@ func main() {
|
||||
|
||||
case "package":
|
||||
//verifyGitRepoIsClean()
|
||||
grunt("release", "--pkgVer="+version)
|
||||
grunt("release")
|
||||
createLinuxPackages()
|
||||
|
||||
case "latest":
|
||||
@ -107,6 +110,16 @@ func readVersionFromPackageJson() {
|
||||
}
|
||||
|
||||
version = jsonObj["version"].(string)
|
||||
linuxPackageVersion = version
|
||||
linuxPackageIteration = ""
|
||||
|
||||
// handle pre version stuff (deb / rpm does not support semver)
|
||||
parts := strings.Split(version, "-")
|
||||
|
||||
if len(parts) > 1 {
|
||||
linuxPackageVersion = parts[0]
|
||||
linuxPackageIteration = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
type linuxPackageOptions struct {
|
||||
@ -208,10 +221,14 @@ func createPackage(options linuxPackageOptions) {
|
||||
"--config-files", options.systemdServiceFilePath,
|
||||
"--after-install", options.postinstSrc,
|
||||
"--name", "grafana",
|
||||
"--version", version,
|
||||
"--version", linuxPackageVersion,
|
||||
"-p", "./dist",
|
||||
}
|
||||
|
||||
if linuxPackageIteration != "" {
|
||||
args = append(args, "--iteration", linuxPackageIteration)
|
||||
}
|
||||
|
||||
// add dependenciesj
|
||||
for _, dep := range options.depends {
|
||||
args = append(args, "--depends", dep)
|
||||
@ -259,6 +276,7 @@ func grunt(params ...string) {
|
||||
|
||||
func setup() {
|
||||
runPrint("go", "get", "-v", "github.com/tools/godep")
|
||||
runPrint("go", "get", "-v", "github.com/blang/semver")
|
||||
runPrint("go", "get", "-v", "github.com/mattn/go-sqlite3")
|
||||
runPrint("go", "install", "-v", "github.com/mattn/go-sqlite3")
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ app_mode = production
|
||||
|
||||
#################################### Paths ####################################
|
||||
[paths]
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is useD)
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
#
|
||||
data = data
|
||||
#
|
||||
@ -62,7 +62,7 @@ path = grafana.db
|
||||
|
||||
#################################### Session ####################################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", default is "memory"
|
||||
# Either "memory", "file", "redis", "mysql", "postgresql", default is "file"
|
||||
provider = file
|
||||
|
||||
# Provider config options
|
||||
@ -70,6 +70,7 @@ provider = file
|
||||
# file: session dir path, is relative to grafana data_path
|
||||
# redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,grafana`
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
|
||||
|
||||
provider_config = sessions
|
||||
|
||||
# Session cookie name
|
||||
@ -139,6 +140,7 @@ enabled = false
|
||||
client_id = some_id
|
||||
client_secret = some_secret
|
||||
scopes = user:email
|
||||
team_ids =
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
api_url = https://api.github.com/user
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
#################################### Paths ####################################
|
||||
[paths]
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is useD)
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
#
|
||||
;data = /var/lib/grafana
|
||||
#
|
||||
@ -62,7 +62,7 @@
|
||||
|
||||
#################################### Session ####################################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", default is "memory"
|
||||
# Either "memory", "file", "redis", "mysql", "postgresql", default is "file"
|
||||
;provider = file
|
||||
|
||||
# Provider config options
|
||||
@ -142,8 +142,8 @@
|
||||
;auth_url = https://github.com/login/oauth/authorize
|
||||
;token_url = https://github.com/login/oauth/access_token
|
||||
;api_url = https://api.github.com/user
|
||||
# Uncomment bellow to only allow specific email domains
|
||||
; allowed_domains = mycompany.com othercompany.com
|
||||
;team_ids =
|
||||
;allowed_domains =
|
||||
|
||||
#################################### Google Auth ##########################
|
||||
[auth.google]
|
||||
@ -154,8 +154,7 @@
|
||||
;auth_url = https://accounts.google.com/o/oauth2/auth
|
||||
;token_url = https://accounts.google.com/o/oauth2/token
|
||||
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
|
||||
# Uncomment bellow to only allow specific email domains
|
||||
; allowed_domains = mycompany.com othercompany.com
|
||||
;allowed_domains =
|
||||
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
|
@ -44,7 +44,7 @@ docs-test: docs-build
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
|
||||
|
||||
docs-build:
|
||||
git fetch https://github.com/grafana/grafana.git docs-1.x && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
|
||||
git fetch https://github.com/grafana/grafana.git docs-2.0 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
|
||||
echo "$(GIT_BRANCH)" > GIT_BRANCH
|
||||
echo "$(GITCOMMIT)" > GITCOMMIT
|
||||
docker build -t "$(DOCKER_DOCS_IMAGE)" .
|
||||
|
@ -4,9 +4,9 @@ page_keywords: grafana, introduction, documentation, about
|
||||
|
||||
# About Grafana
|
||||
|
||||
Grafana is a leading open source applications for visualizing large-scale measurement data.
|
||||
Grafana is a leading open source applications for visualizing large-scale measurement data.
|
||||
|
||||
It provides a powerful and elegant way to create, share, and explore data and dashboards from your disparate metric databases, either with your team or the world.
|
||||
It provides a powerful and elegant way to create, share, and explore data and dashboards from your disparate metric databases, either with your team or the world.
|
||||
|
||||
Grafana is most commonly used for Internet infrastructure and application analytics, but many use it in other domains including industrial sensors, home automation, weather, and process control.
|
||||
|
||||
@ -16,7 +16,7 @@ Version 2.0 was released in April 2015: Grafana now ships with its own backend s
|
||||
|
||||
## Community Resources, Feedback, and Support
|
||||
|
||||
Thousands of organizations large and small rely on Grafana, and we have a vibrant and active community that constantly inspires us.
|
||||
Thousands of organizations large and small rely on Grafana, and we have a vibrant and active community that constantly inspires us.
|
||||
|
||||
Please don't hesitate to [open a new issue on Github](https://github.com/grafana/grafana/issues) with your suggestions, ideas, and bug reports.
|
||||
|
||||
@ -35,4 +35,4 @@ If you have any trouble with Grafana, whether you can't get it set up or you jus
|
||||
|
||||
## License
|
||||
|
||||
By utilizing this software, you agree to the terms of the included license. Grafana is licensed under the Apache 2.0 agreement. See [LICENSE](https://github.com/grafana/grafana/blob/master/LICENSE.mdhttps://github.com/grafana/grafana/blob/master/LICENSE.md) for the full license terms.
|
||||
By utilizing this software, you agree to the terms of the included license. Grafana is licensed under the Apache 2.0 agreement. See [LICENSE](https://github.com/grafana/grafana/blob/master/LICENSE.md) for the full license terms.
|
||||
|
@ -179,6 +179,7 @@ Client ID and a Client Secret. Specify these in the grafana config file. Example
|
||||
client_id = YOUR_GITHUB_APP_CLIENT_ID
|
||||
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
|
||||
scopes = user:email
|
||||
team_ids =
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
allow_sign_up = false
|
||||
@ -189,6 +190,21 @@ now login or signup with your github accounts.
|
||||
You may allow users to sign-up via github auth by setting allow_sign_up to true. When this option is
|
||||
set to true, any user successfully authenticating via github auth will be automatically signed up.
|
||||
|
||||
### team_ids
|
||||
Require an active team membership for at least one of the given teams on GitHub.
|
||||
If the authenticated user isn't a member of at least one the teams they will not
|
||||
be able to register or authenticate with your Grafana instance. Example:
|
||||
|
||||
[auth.github]
|
||||
enabled = true
|
||||
client_id = YOUR_GITHUB_APP_CLIENT_ID
|
||||
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
|
||||
scopes = user:email
|
||||
team_ids = 150,300
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
allow_sign_up = false
|
||||
|
||||
## [auth.google]
|
||||
You need to create a google project. You can do this in the [Google Developer Console](https://console.developers.google.com/project).
|
||||
When you create the project you will need to specify a callback URL. Specify this as callback:
|
||||
@ -219,7 +235,7 @@ set to true, any user successfully authenticating via google auth will be automa
|
||||
## [session]
|
||||
|
||||
### provider
|
||||
Valid values are "memory", "file", "mysql", 'postgres'. Default is "memory".
|
||||
Valid values are "memory", "file", "mysql", 'postgres'. Default is "file".
|
||||
|
||||
### provider_config
|
||||
This option should be configured differently depending on what type of session provider you have configured.
|
||||
@ -252,10 +268,8 @@ How long sessions lasts in seconds. Defaults to `86400` (24 hours).
|
||||
When enabled Grafana will send anonymous usage statistics to stats.grafana.org.
|
||||
No ip addresses are being tracked, only simple counters to track running instances,
|
||||
versions, dashboard & error counts. It is very helpful to us, please leave this
|
||||
enabled. Counters are sent every 24 hours.
|
||||
enabled. Counters are sent every 24 hours. Default value is `true`.
|
||||
|
||||
### google_analytics_ua_id
|
||||
If you want to track Grafana usage via Google analytics specify *your* Univeral Analytics ID
|
||||
here. By defualt this feature is disabled.
|
||||
|
||||
|
||||
|
@ -10,11 +10,11 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
.deb for Debian-based Linux | [grafana_2.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.1_amd64.deb)
|
||||
.deb for Debian-based Linux | [grafana_2.0.2_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb)
|
||||
|
||||
## Install
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.1_amd64.deb
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_2.0.2_amd64.deb
|
||||
|
||||
|
@ -10,12 +10,12 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.1-1.x86_64.rpm)
|
||||
.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.0.2-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm)
|
||||
|
||||
## Install
|
||||
You can install using yum
|
||||
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.1-1.x86_64.rpm
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm
|
||||
|
||||
Or manually using `rpm`
|
||||
|
||||
@ -30,9 +30,9 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
||||
name=grafana
|
||||
baseurl=https://packagecloud.io/grafana/stable/el/6/$basearch
|
||||
repo_gpgcheck=1
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
gpgkey=https://packagecloud.io/gpg.key
|
||||
gpgcheck=1
|
||||
gpgkey=https://packagecloud.io/gpg.key https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana
|
||||
sslverify=1
|
||||
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
|
||||
|
||||
|
@ -6,8 +6,23 @@ page_keywords: grafana, installation, windows guide
|
||||
|
||||
# Installing on Windows
|
||||
|
||||
There are currently no binary build for Windows. But read the [build from source](../project/building_from_source)
|
||||
page for instructions on how to build it yourself.
|
||||
## Download
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Zip package for Windows | [grafana.2.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.0.2.windows-x64.zip)
|
||||
|
||||
## Configure
|
||||
The zip file contains a folder with the current grafana version. Extract this folder to anywhere you want Grafana to run from.
|
||||
Go into the `conf` directory and copy `sample.ini` to `custom.ini`. You should edit `custom.ini`, never `defaults.ini`.
|
||||
|
||||
The default grafana port is `3000`, this port requires extra permissions on windows. Edit `custom.ini` and uncomment the `http_port`
|
||||
config and change it to something like `8080` or similar. That port should not require extra windows privileges.
|
||||
|
||||
Start grafana by executing `grafana-server.exe`, preferbly from the command line. If you want to run Grafana as
|
||||
windows service, download [NSSM](https://nssm.cc/). It is very easy add grafana as windows service using that tool.
|
||||
|
||||
Read more about the [configuration options](configuration.md).
|
||||
|
||||
## Building on Windows
|
||||
|
||||
|
@ -6,5 +6,147 @@ page_keywords: grafana, admin, http, api, documentation
|
||||
|
||||
# HTTP API Reference
|
||||
|
||||
This documentation page has yet to be written.
|
||||
The Grafana backend exposes an HTTP API, the same API is used by the frontend to do everything from saving
|
||||
dashboards, creating users and updating data sources.
|
||||
|
||||
## Authorization
|
||||
|
||||
Currently you can authenticate via an `API Token` or via a `Session cookie` (acquired using regular login or oauth).
|
||||
|
||||
### Create API Token
|
||||
|
||||
Open the sidemenu and click the organization dropdown and select the `API Keys` option.
|
||||
|
||||
![](/img/v2/orgdropdown_api_keys.png)
|
||||
|
||||
You use the token in all requests in the `Authorization` header, like this:
|
||||
|
||||
**Example**:
|
||||
|
||||
GET http://your.grafana.com/api/dashboards/db/mydash HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
The `Authorization` header value should be `Bearer <your api key>`.
|
||||
|
||||
## Dashboards
|
||||
|
||||
### Create / Update dashboard
|
||||
|
||||
`POST /api/dashboards/db`
|
||||
|
||||
Creates a new dashboard or updates an existing dashboard.
|
||||
|
||||
**Example Request for new dashboard**:
|
||||
|
||||
POST /api/dashboards/db HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"dashboard": {
|
||||
"id": null,
|
||||
"title": "Production Overview",
|
||||
"tags": [ "templated" ],
|
||||
"timezone": "browser",
|
||||
"rows": [
|
||||
{
|
||||
}
|
||||
]
|
||||
"schemaVersion": 6,
|
||||
"version": 0
|
||||
},
|
||||
"overwrite": false
|
||||
}
|
||||
|
||||
JSON Body schema:
|
||||
|
||||
- **dashboard** – The complete dashboard model, id = null to create a new dashboard
|
||||
- **overwrite** – Set to true if you want to overwrite existing dashboard with newer version or with same dashboard title.
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
Content-Length: 78
|
||||
|
||||
{
|
||||
"slug": "production-overview",
|
||||
"status": "success",
|
||||
"version": 1
|
||||
}
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **200** – Created
|
||||
- **400** – Errors (invalid json, missing or invalid fields, etc)
|
||||
- **401** – Unauthorized
|
||||
- **412** – Precondition failed
|
||||
|
||||
The **412** status code is used when a newer dashboard already exists (newer, its version is greater than the verison that was sent). The
|
||||
same status code is also used if another dashboar exists with the same title. The response body will look like this:
|
||||
|
||||
HTTP/1.1 412 Precondition Failed
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
Content-Length: 97
|
||||
|
||||
{
|
||||
"message": "The dashboard has been changed by someone else",
|
||||
"status": "version-mismatch"
|
||||
}
|
||||
|
||||
In in case of title already exists the `status` property will be `name-exists`.
|
||||
|
||||
### Get dashboard
|
||||
|
||||
`GET /api/dashboards/db/:slug`
|
||||
|
||||
Will return the dashboard given the dashboard slug. Slug is the url friendly version of the dashboard title.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
GET /api/dashboards/db/production-overview HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"meta": {
|
||||
"isStarred": false,
|
||||
"slug": "production-overview"
|
||||
},
|
||||
"dashboard": {
|
||||
"id": null,
|
||||
"title": "Production Overview",
|
||||
"tags": [ "templated" ],
|
||||
"timezone": "browser",
|
||||
"rows": [
|
||||
{
|
||||
}
|
||||
]
|
||||
"schemaVersion": 6,
|
||||
"version": 0
|
||||
},
|
||||
}
|
||||
|
||||
### Delete dashboard
|
||||
|
||||
`DELETE /api/dashboards/db/:slug`
|
||||
|
||||
The above will delete the dashboard with the specified slug. The slug is the url friendly (unique) version of the dashboard title.
|
||||
|
||||
## Data sources
|
||||
|
||||
### Create data source
|
||||
|
||||
## Organizations
|
||||
|
||||
## Users
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ All of this applies to all Panels in the Dashboard (except those with Panel Time
|
||||
|
||||
It's possible to customize the options displayed for relative time and the auto-refresh options.
|
||||
|
||||
From Dashboard setttings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis.
|
||||
From Dashboard setttings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis. Entries are comma seperated and accept a number followed by one of the following units: s (seconds), m (minutes), h (hours), d (days), w (weeks), M (months), y (years).
|
||||
|
||||
![](/img/v1/timepicker_editor.png)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.0.0-beta3",
|
||||
"version": "2.0.1",
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "2.0.1",
|
||||
"version": "2.1.0-pre1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
|
@ -87,6 +87,7 @@ case "$1" in
|
||||
# check if pid file has been written two
|
||||
if ! [[ -s $PID_FILE ]]; then
|
||||
log_end_msg 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
i=0
|
||||
@ -96,7 +97,10 @@ case "$1" in
|
||||
do
|
||||
sleep 1
|
||||
i=$(($i + 1))
|
||||
[ $i -gt $timeout ] && log_end_msg 1
|
||||
if [ $i -gt $timeout ]; then
|
||||
log_end_msg 1
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
log_end_msg $return
|
||||
|
@ -144,5 +144,3 @@ case "$1" in
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
@ -21,16 +21,17 @@ type CurrentUser struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
}
|
||||
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool `json:"isStarred"`
|
||||
IsHome bool `json:"isHome"`
|
||||
IsSnapshot bool `json:"isSnapshot"`
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsHome bool `json:"isHome,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Slug string `json:"slug"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
|
@ -18,6 +18,7 @@ func setIndexViewData(c *middleware.Context) error {
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
LightTheme: c.Theme == "light",
|
||||
OrgId: c.OrgId,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@ -45,7 +46,11 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
|
||||
userInfo, err := connect.UserInfo(token)
|
||||
if err != nil {
|
||||
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
|
||||
if err == social.ErrMissingTeamMembership {
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github team membership not fulfilled"))
|
||||
} else {
|
||||
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -54,7 +59,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
// validate that the email is allowed to login to grafana
|
||||
if !connect.IsEmailAllowed(userInfo.Email) {
|
||||
log.Info("OAuth login attempt with unallowed email, %s", userInfo.Email)
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?email_not_allowed=1")
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required email domain not fulfilled"))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/dalu/slug"
|
||||
)
|
||||
|
||||
// Typed errors
|
||||
|
@ -86,4 +86,10 @@ func addDashboardMigration(mg *Migrator) {
|
||||
}))
|
||||
|
||||
mg.AddMigration("drop table dashboard_v1", NewDropTableMigration("dashboard_v1"))
|
||||
|
||||
// change column type of dashboard.data
|
||||
mg.AddMigration("alter dashboard.data to mediumtext v1", new(RawSqlMigration).
|
||||
Sqlite("SELECT 0 WHERE 0;").
|
||||
Postgres("SELECT 0;").
|
||||
Mysql("ALTER TABLE dashboard MODIFY data MEDIUMTEXT;"))
|
||||
}
|
||||
|
@ -48,4 +48,10 @@ func addDashboardSnapshotMigrations(mg *Migrator) {
|
||||
|
||||
mg.AddMigration("create dashboard_snapshot table v5 #2", NewAddTableMigration(snapshotV5))
|
||||
addTableIndicesMigrations(mg, "v5", snapshotV5)
|
||||
|
||||
// change column type of dashboard
|
||||
mg.AddMigration("alter dashboard_snapshot to mediumtext v2", new(RawSqlMigration).
|
||||
Sqlite("SELECT 0 WHERE 0;").
|
||||
Postgres("SELECT 0;").
|
||||
Mysql("ALTER TABLE dashboard_snapshot MODIFY dashboard MEDIUMTEXT;"))
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ func (m *MigrationBase) GetCondition() MigrationCondition {
|
||||
type RawSqlMigration struct {
|
||||
MigrationBase
|
||||
|
||||
sqlite string
|
||||
mysql string
|
||||
sqlite string
|
||||
mysql string
|
||||
postgres string
|
||||
}
|
||||
|
||||
func (m *RawSqlMigration) Sql(dialect Dialect) string {
|
||||
@ -35,6 +36,8 @@ func (m *RawSqlMigration) Sql(dialect Dialect) string {
|
||||
return m.mysql
|
||||
case SQLITE:
|
||||
return m.sqlite
|
||||
case POSTGRES:
|
||||
return m.postgres
|
||||
}
|
||||
|
||||
panic("db type not supported")
|
||||
@ -50,6 +53,11 @@ func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *RawSqlMigration) Postgres(sql string) *RawSqlMigration {
|
||||
m.postgres = sql
|
||||
return m
|
||||
}
|
||||
|
||||
type AddColumnMigration struct {
|
||||
MigrationBase
|
||||
tableName string
|
||||
|
@ -2,7 +2,9 @@ package social
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -75,13 +77,24 @@ func NewOAuthService() {
|
||||
// GitHub.
|
||||
if name == "github" {
|
||||
setting.OAuthService.GitHub = true
|
||||
SocialMap["github"] = &SocialGithub{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup}
|
||||
teamIds := sec.Key("team_ids").Ints(",")
|
||||
SocialMap["github"] = &SocialGithub{
|
||||
Config: &config,
|
||||
allowedDomains: info.AllowedDomains,
|
||||
apiUrl: info.ApiUrl,
|
||||
allowSignup: info.AllowSignup,
|
||||
teamIds: teamIds,
|
||||
}
|
||||
}
|
||||
|
||||
// Google.
|
||||
if name == "google" {
|
||||
setting.OAuthService.Google = true
|
||||
SocialMap["google"] = &SocialGoogle{Config: &config, allowedDomains: info.AllowedDomains, ApiUrl: info.ApiUrl, allowSignup: info.AllowSignup}
|
||||
SocialMap["google"] = &SocialGoogle{
|
||||
Config: &config, allowedDomains: info.AllowedDomains,
|
||||
apiUrl: info.ApiUrl,
|
||||
allowSignup: info.AllowSignup,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,10 +116,15 @@ func isEmailAllowed(email string, allowedDomains []string) bool {
|
||||
type SocialGithub struct {
|
||||
*oauth2.Config
|
||||
allowedDomains []string
|
||||
ApiUrl string
|
||||
apiUrl string
|
||||
allowSignup bool
|
||||
teamIds []int
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
|
||||
)
|
||||
|
||||
func (s *SocialGithub) Type() int {
|
||||
return int(models.GITHUB)
|
||||
}
|
||||
@ -119,6 +137,28 @@ func (s *SocialGithub) IsSignupAllowed() bool {
|
||||
return s.allowSignup
|
||||
}
|
||||
|
||||
func (s *SocialGithub) IsTeamMember(client *http.Client, username string, teamId int) bool {
|
||||
var data struct {
|
||||
Url string `json:"url"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
membershipUrl := fmt.Sprintf("https://api.github.com/teams/%d/memberships/%s", teamId, username)
|
||||
r, err := client.Get(membershipUrl)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
active := data.State == "active"
|
||||
return active
|
||||
}
|
||||
|
||||
func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
var data struct {
|
||||
Id int `json:"id"`
|
||||
@ -128,7 +168,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
|
||||
var err error
|
||||
client := s.Client(oauth2.NoContext, token)
|
||||
r, err := client.Get(s.ApiUrl)
|
||||
r, err := client.Get(s.apiUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -139,11 +179,23 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BasicUserInfo{
|
||||
userInfo := &BasicUserInfo{
|
||||
Identity: strconv.Itoa(data.Id),
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(s.teamIds) > 0 {
|
||||
for _, teamId := range s.teamIds {
|
||||
if s.IsTeamMember(client, data.Name, teamId) {
|
||||
return userInfo, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrMissingTeamMembership
|
||||
} else {
|
||||
return userInfo, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ________ .__
|
||||
@ -156,7 +208,7 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
type SocialGoogle struct {
|
||||
*oauth2.Config
|
||||
allowedDomains []string
|
||||
ApiUrl string
|
||||
apiUrl string
|
||||
allowSignup bool
|
||||
}
|
||||
|
||||
@ -181,7 +233,7 @@ func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
var err error
|
||||
|
||||
client := s.Client(oauth2.NoContext, token)
|
||||
r, err := client.Get(s.ApiUrl)
|
||||
r, err := client.Get(s.apiUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -380,6 +380,9 @@ function($, _, moment) {
|
||||
kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']);
|
||||
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
|
||||
kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', ' GJ', ' TJ', ' PJ', ' EJ', ' ZJ', ' YJ']);
|
||||
kbn.valueFormats.amp = kbn.formatFuncCreator(1000, [' A', ' kA', ' MA', ' GA', ' TA', ' PA', ' EA', ' ZA', ' YA']);
|
||||
kbn.valueFormats.volt = kbn.formatFuncCreator(1000, [' V', ' kV', ' MV', ' GV', ' TV', ' PV', ' EV', ' ZV', ' YV']);
|
||||
kbn.valueFormats.hertz = kbn.formatFuncCreator(1000, [' Hz', ' kHz', ' MHz', ' GHz', ' THz', ' PHz', ' EHz', ' ZHz', ' YHz']);
|
||||
kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
|
||||
kbn.valueFormats.kwatt = kbn.formatFuncCreator(1000, [' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
|
||||
kbn.valueFormats.watth = kbn.formatFuncCreator(1000, [' Wh', ' kWh', ' MWh', ' GWh', ' TWh', ' PWh', ' EWh', ' ZWh', ' YWh']);
|
||||
@ -534,6 +537,7 @@ function($, _, moment) {
|
||||
{text: 'microseconds (µs)', value: 'µs'},
|
||||
{text: 'milliseconds (ms)', value: 'ms'},
|
||||
{text: 'seconds (s)', value: 's'},
|
||||
{text: 'Hertz (1/s)', value: 'hertz'},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -561,6 +565,8 @@ function($, _, moment) {
|
||||
{text: 'kilowatt-hour (kWh)', value: 'kwatth'},
|
||||
{text: 'joule (J)', value: 'joule'},
|
||||
{text: 'electron volt (eV)', value: 'ev'},
|
||||
{text: 'Ampere (A)', value: 'amp'},
|
||||
{text: 'Volt (V)', value: 'volt'},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ function (angular, config) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('LoginCtrl', function($scope, backendSrv, contextSrv) {
|
||||
module.controller('LoginCtrl', function($scope, backendSrv, contextSrv, $location) {
|
||||
$scope.formModel = {
|
||||
user: '',
|
||||
email: '',
|
||||
@ -28,6 +28,13 @@ function (angular, config) {
|
||||
$scope.init = function() {
|
||||
$scope.$watch("loginMode", $scope.loginModeChanged);
|
||||
$scope.passwordChanged();
|
||||
|
||||
var params = $location.search();
|
||||
if (params.failedMsg) {
|
||||
$scope.appEvent('alert-warning', ['Login Failed', params.failedMsg]);
|
||||
delete params.failedMsg;
|
||||
$location.search(params);
|
||||
}
|
||||
};
|
||||
|
||||
// build info view model
|
||||
|
@ -52,6 +52,10 @@ function (angular, _) {
|
||||
};
|
||||
|
||||
$scope.saveDashboard = function(options) {
|
||||
if ($scope.dashboardMeta.canSave === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var clone = $scope.dashboard.getSaveModelClone();
|
||||
|
||||
backendSrv.saveDashboard(clone, options).then(function(data) {
|
||||
@ -119,6 +123,8 @@ function (angular, _) {
|
||||
$scope.saveDashboardAs = function() {
|
||||
var newScope = $rootScope.$new();
|
||||
newScope.clone = $scope.dashboard.getSaveModelClone();
|
||||
newScope.clone.editable = true;
|
||||
newScope.clone.hideControls = false;
|
||||
|
||||
$scope.appEvent('show-modal', {
|
||||
src: './app/features/dashboard/partials/saveDashboardAs.html',
|
||||
|
@ -52,24 +52,22 @@ function (angular, $, kbn, _, moment) {
|
||||
|
||||
p._initMeta = function(meta) {
|
||||
meta = meta || {};
|
||||
meta.canShare = true;
|
||||
meta.canSave = true;
|
||||
meta.canEdit = true;
|
||||
meta.canStar = true;
|
||||
|
||||
meta.canShare = meta.canShare === false ? false : true;
|
||||
meta.canSave = meta.canSave === false ? false : true;
|
||||
meta.canEdit = meta.canEdit === false ? false : true;
|
||||
meta.canStar = meta.canStar === false ? false : true;
|
||||
meta.canDelete = meta.canDelete === false ? false : true;
|
||||
|
||||
if (contextSrv.hasRole('Viewer')) {
|
||||
meta.canSave = false;
|
||||
}
|
||||
|
||||
if (meta.isSnapshot) {
|
||||
meta.canSave = false;
|
||||
}
|
||||
|
||||
if (meta.isHome) {
|
||||
meta.canShare = false;
|
||||
meta.canStar = false;
|
||||
meta.canSave = false;
|
||||
if (!this.editable) {
|
||||
meta.canEdit = false;
|
||||
meta.canDelete = false;
|
||||
meta.canSave = false;
|
||||
this.hideControls = true;
|
||||
}
|
||||
|
||||
this.meta = meta;
|
||||
|
@ -30,16 +30,16 @@
|
||||
<li ng-show="dashboardMeta.canSave">
|
||||
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||
</li>
|
||||
<li class="dropdown" ng-if="dashboardMeta.canEdit">
|
||||
<li class="dropdown">
|
||||
<a class="pointer" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
|
||||
<li><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
|
||||
<li><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
|
||||
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
|
||||
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
|
||||
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
|
||||
<li><a class="pointer" ng-click="exportDashboard();">Export</a></li>
|
||||
<li><a class="pointer" ng-click="editJson();">View JSON</a></li>
|
||||
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
|
||||
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
|
||||
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
|
||||
<li ng-if="dashboardMeta.canDelete"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -37,7 +37,7 @@ function(angular, _, config) {
|
||||
});
|
||||
|
||||
this.ignoreChanges = function() {
|
||||
if (!self.current) { return true; }
|
||||
if (!self.current || !self.current.meta) { return true; }
|
||||
|
||||
var meta = self.current.meta;
|
||||
return !meta.canSave || meta.fromScript || meta.fromFile;
|
||||
|
@ -43,7 +43,7 @@ function (angular, _, kbn, $) {
|
||||
}
|
||||
|
||||
if (scope.panel.timeShift) {
|
||||
if (!kbn.isValidTimeSpan(scope.panel.timeFrom)) {
|
||||
if (!kbn.isValidTimeSpan(scope.panel.timeShift)) {
|
||||
scope.panelMeta.timeInfo = 'invalid timeshift';
|
||||
return;
|
||||
}
|
||||
|
@ -70,6 +70,14 @@ function (angular, _, config) {
|
||||
};
|
||||
|
||||
$scope.toggleFullscreen = function(edit) {
|
||||
if (edit && $scope.dashboardMeta.canEdit === false) {
|
||||
$scope.appEvent('alert-warning', [
|
||||
'Dashboard not editable',
|
||||
'Use Save As.. feature to create an editable copy of this dashboard.'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
|
||||
};
|
||||
|
||||
|
@ -29,13 +29,7 @@ function (angular, _, kbn) {
|
||||
var variable = this.variables[i];
|
||||
var urlValue = queryParams['var-' + variable.name];
|
||||
if (urlValue !== void 0) {
|
||||
var option = _.findWhere(variable.options, { text: urlValue });
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
|
||||
var promise = this.setVariableValue(variable, option, true);
|
||||
this.updateAutoInterval(variable);
|
||||
|
||||
promises.push(promise);
|
||||
promises.push(this.setVariableFromUrl(variable, urlValue));
|
||||
}
|
||||
else if (variable.refresh) {
|
||||
promises.push(this.updateOptions(variable));
|
||||
@ -48,11 +42,30 @@ function (angular, _, kbn) {
|
||||
return $q.all(promises);
|
||||
};
|
||||
|
||||
this.setVariableFromUrl = function(variable, urlValue) {
|
||||
if (variable.refresh) {
|
||||
var self = this;
|
||||
//refresh the list of options before setting the value
|
||||
return this.updateOptions(variable).then(function() {
|
||||
var option = _.findWhere(variable.options, { text: urlValue });
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
|
||||
self.updateAutoInterval(variable);
|
||||
return self.setVariableValue(variable, option);
|
||||
});
|
||||
}
|
||||
var option = _.findWhere(variable.options, { text: urlValue });
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
|
||||
this.updateAutoInterval(variable);
|
||||
return this.setVariableValue(variable, option);
|
||||
};
|
||||
|
||||
this.updateAutoInterval = function(variable) {
|
||||
if (!variable.auto) { return; }
|
||||
|
||||
// add auto option if missing
|
||||
if (variable.options[0].text !== 'auto') {
|
||||
if (variable.options.length && variable.options[0].text !== 'auto') {
|
||||
variable.options.unshift({ text: 'auto', value: '$__auto_interval' });
|
||||
}
|
||||
|
||||
|
@ -228,10 +228,10 @@
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px">
|
||||
<li class="tight-form-item" style="width: 105px">
|
||||
<strong>Decimals</strong>
|
||||
</li>
|
||||
<li>
|
||||
<li style="width: 105px">
|
||||
<input type="number" class="input-small tight-form-input" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
|
||||
ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
|
||||
</li>
|
||||
@ -242,4 +242,3 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -63,12 +63,13 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function getLegendHeight() {
|
||||
function getLegendHeight(panelHeight) {
|
||||
if (!scope.panel.legend.show || scope.panel.legend.rightSide) {
|
||||
return 0;
|
||||
}
|
||||
if (scope.panel.legend.alignAsTable) {
|
||||
return 30 + (25 * data.length);
|
||||
var total = 30 + (25 * data.length);
|
||||
return Math.min(total, Math.floor(panelHeight/2));
|
||||
} else {
|
||||
return 26;
|
||||
}
|
||||
@ -84,7 +85,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
graphHeight -= 5; // padding
|
||||
graphHeight -= scope.panel.title ? 24 : 9; // subtract panel title bar
|
||||
|
||||
graphHeight = graphHeight - getLegendHeight(); // subtract one line legend
|
||||
graphHeight = graphHeight - getLegendHeight(graphHeight); // subtract one line legend
|
||||
|
||||
elem.css('height', graphHeight + 'px');
|
||||
|
||||
|
@ -29,7 +29,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
|
||||
panelName: 'Graph',
|
||||
editIcon: "fa fa-bar-chart",
|
||||
fullscreen: true,
|
||||
metricsEditor: true
|
||||
metricsEditor: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
|
||||
@ -67,9 +67,9 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
|
||||
// show/hide lines
|
||||
lines : true,
|
||||
// fill factor
|
||||
fill : 0,
|
||||
fill : 1,
|
||||
// line width in pixels
|
||||
linewidth : 1,
|
||||
linewidth : 2,
|
||||
// show hide points
|
||||
points : false,
|
||||
// point radius in pixels
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="row-text pointer" ng-click="toggle_row(row)" ng-bind="row.title"></div>
|
||||
</div>
|
||||
<div class="row-open" ng-show="!row.collapse">
|
||||
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
|
||||
<div class='row-tab bgSuccess dropdown' ng-show="dashboardMeta.canEdit">
|
||||
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-bars"></i>
|
||||
</span>
|
||||
@ -99,7 +99,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show='dashboard.editable' class="row-fluid add-row-panel-hint">
|
||||
<div ng-show='dashboardMeta.canEdit' class="row-fluid add-row-panel-hint">
|
||||
<div class="span12" style="text-align:right;">
|
||||
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-small">
|
||||
<span><i class="fa fa-plus"></i> ADD ROW</span>
|
||||
|
@ -17,63 +17,118 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gf-box-body">
|
||||
|
||||
<div ng-if="editor.index == 0">
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.title'></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="dashboard.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<editor-opt-bool text="Hide controls (CTRL+H)" model="dashboard.hideControls"></editor-opt-bool>
|
||||
<editor-opt-bool text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-opt-bool>
|
||||
<div class="gf-box-body" style="padding-bottom: 50px;">
|
||||
<div ng-if="editor.index == 0">
|
||||
<div class="editor-row">
|
||||
<div class="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-xlarge 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>
|
||||
<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">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 90px">
|
||||
Timezone
|
||||
</li>
|
||||
<li>
|
||||
<select ng-model="dashboard.timezone" class='input-small tight-form-input' ng-options="f for f in ['browser','utc']"></select>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Tags</label>
|
||||
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
<tip>Press enter to a add tag</tip>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Toggles</h5>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 181px">
|
||||
<label class="checkbox-label" for="dashboard.editable">Editable</label>
|
||||
</li>
|
||||
<li>
|
||||
<li class="tight-form-item last">
|
||||
<input class="cr1" id="dashboard.editable" type="checkbox" ng-model="dashboard.editable" ng-checked="dashboard.editable">
|
||||
<label for="dashboard.editable" class="cr1"></label>
|
||||
</li>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 181px">
|
||||
<label class="checkbox-label" for="dashboard.hideControls">Hide Controls (CTRL+H)</label>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<input class="cr1" id="dashboard.hideControls" type="checkbox" ng-model="dashboard.hideControls" ng-checked="dashboard.hideControls">
|
||||
<label for="dashboard.hideControls" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 181px">
|
||||
<label class="checkbox-label" for="dashboard.sharedCrosshair">Shared Crosshair (CTRL+H)</label>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<input class="cr1" id="dashboard.sharedCrosshair" type="checkbox" ng-model="dashboard.sharedCrosshair" ng-checked="dashboard.sharedCrosshair">
|
||||
<label for="dashboard.sharedCrosshair" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 1">
|
||||
<div class="editor-row">
|
||||
<div class="span6">
|
||||
<table class="grafana-options-table">
|
||||
<tr ng-repeat="row in dashboard.rows">
|
||||
<td style="width: 97%">
|
||||
{{row.title}}
|
||||
</td>
|
||||
<td><i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
|
||||
<td><i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
|
||||
<td>
|
||||
<a ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 2+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="pulldownEditorPath(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div ng-if="editor.index == 1">
|
||||
<div class="editor-row">
|
||||
<div class="span6">
|
||||
<table class="grafana-options-table">
|
||||
<tr ng-repeat="row in dashboard.rows">
|
||||
<td style="width: 97%">
|
||||
{{row.title}}
|
||||
</td>
|
||||
<td><i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
|
||||
<td><i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
|
||||
<td>
|
||||
<a ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 2+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="pulldownEditorPath(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="gf-box-footer">
|
||||
|
@ -74,7 +74,7 @@ function (angular, _, config, kbn, moment) {
|
||||
var data = {
|
||||
"fields": [timeField, "_source"],
|
||||
"query" : { "filtered": { "query" : query, "filter": filter } },
|
||||
"size": 100
|
||||
"size": 10000
|
||||
};
|
||||
|
||||
return this._request('POST', '/_search', annotation.index, data).then(function(results) {
|
||||
|
@ -6,12 +6,11 @@
|
||||
|
||||
<p>
|
||||
This is just a test data source that generates random walk series. If this is your only data source
|
||||
open the left side menu and navigate to the data sources admin screen and add your data sources. You can change
|
||||
data source using the button to the left of the <strong>Add query</strong> button.
|
||||
open the left side menu and navigate to the data sources admin screen and add your data sources (you need to be
|
||||
logged in to do this). You can change data source using the button to the left of the <strong>Add query</strong> button.
|
||||
</p>
|
||||
</div>
|
||||
<div class="span2"></div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
|
@ -18,6 +18,8 @@ function (angular, _, kbn, moment, $) {
|
||||
|
||||
if (!$routeParams.slug) {
|
||||
backendSrv.get('/api/dashboards/home').then(function(result) {
|
||||
var meta = result.meta;
|
||||
meta.canSave = meta.canShare = meta.canEdit = meta.canStar = false;
|
||||
$scope.initDashboard(result, $scope);
|
||||
},function() {
|
||||
dashboardLoadFailed('Not found');
|
||||
@ -38,7 +40,16 @@ function (angular, _, kbn, moment, $) {
|
||||
backendSrv.get('/api/snapshots/' + $routeParams.key).then(function(result) {
|
||||
$scope.initDashboard(result, $scope);
|
||||
}, function() {
|
||||
$scope.initDashboard({meta: {isSnapshot: true}, model: {title: 'Snapshot not found'}}, $scope);
|
||||
$scope.initDashboard({
|
||||
meta: {
|
||||
isSnapshot: true,
|
||||
canSave: false,
|
||||
canEdit: false,
|
||||
},
|
||||
model: {
|
||||
title: 'Snapshot not found'
|
||||
}
|
||||
}, $scope);
|
||||
});
|
||||
});
|
||||
|
||||
@ -48,15 +59,18 @@ function (angular, _, kbn, moment, $) {
|
||||
$location.path('');
|
||||
return;
|
||||
}
|
||||
$scope.initDashboard({meta: {}, model: window.grafanaImportDashboard }, $scope);
|
||||
$scope.initDashboard({
|
||||
meta: { canShare: false, canStar: false },
|
||||
model: window.grafanaImportDashboard
|
||||
}, $scope);
|
||||
});
|
||||
|
||||
module.controller('NewDashboardCtrl', function($scope) {
|
||||
$scope.initDashboard({
|
||||
meta: {},
|
||||
meta: { canStar: false, canShare: false },
|
||||
model: {
|
||||
title: "New dashboard",
|
||||
rows: [{ height: '250px', panels:[] }]
|
||||
rows: [{ height: '250px', panels:[] }]
|
||||
},
|
||||
}, $scope);
|
||||
});
|
||||
@ -66,10 +80,10 @@ function (angular, _, kbn, moment, $) {
|
||||
var file_load = function(file) {
|
||||
return $http({
|
||||
url: "public/dashboards/"+file.replace(/\.(?!json)/,"/")+'?' + new Date().getTime(),
|
||||
method: "GET",
|
||||
transformResponse: function(response) {
|
||||
return angular.fromJson(response);
|
||||
}
|
||||
method: "GET",
|
||||
transformResponse: function(response) {
|
||||
return angular.fromJson(response);
|
||||
}
|
||||
}).then(function(result) {
|
||||
if(!result) {
|
||||
return false;
|
||||
@ -82,7 +96,10 @@ function (angular, _, kbn, moment, $) {
|
||||
};
|
||||
|
||||
file_load($routeParams.jsonFile).then(function(result) {
|
||||
$scope.initDashboard({meta: {fromFile: true}, model: result}, $scope);
|
||||
$scope.initDashboard({
|
||||
meta: { canSave: false, canDelete: false },
|
||||
model: result
|
||||
}, $scope);
|
||||
});
|
||||
|
||||
});
|
||||
@ -92,8 +109,8 @@ function (angular, _, kbn, moment, $) {
|
||||
var execute_script = function(result) {
|
||||
var services = {
|
||||
dashboardSrv: dashboardSrv,
|
||||
datasourceSrv: datasourceSrv,
|
||||
$q: $q,
|
||||
datasourceSrv: datasourceSrv,
|
||||
$q: $q,
|
||||
};
|
||||
|
||||
/*jshint -W054 */
|
||||
@ -118,16 +135,19 @@ function (angular, _, kbn, moment, $) {
|
||||
var url = 'public/dashboards/'+file.replace(/\.(?!js)/,"/") + '?' + new Date().getTime();
|
||||
|
||||
return $http({ url: url, method: "GET" })
|
||||
.then(execute_script)
|
||||
.then(null,function(err) {
|
||||
console.log('Script dashboard error '+ err);
|
||||
$scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]);
|
||||
return false;
|
||||
});
|
||||
.then(execute_script)
|
||||
.then(null,function(err) {
|
||||
console.log('Script dashboard error '+ err);
|
||||
$scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
script_load($routeParams.jsFile).then(function(result) {
|
||||
$scope.initDashboard({meta: {fromScript: true}, model: result.data}, $scope);
|
||||
$scope.initDashboard({
|
||||
meta: {fromScript: true, canDelete: false, canSave: false},
|
||||
model: result.data
|
||||
}, $scope);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -63,8 +63,9 @@ function (angular, _, config) {
|
||||
var requestIsLocal = options.url.indexOf('/') === 0;
|
||||
var firstAttempt = options.retry === 0;
|
||||
|
||||
if (requestIsLocal && firstAttempt) {
|
||||
if (requestIsLocal && !options.hasSubUrl) {
|
||||
options.url = config.appSubUrl + options.url;
|
||||
options.hasSubUrl = true;
|
||||
}
|
||||
|
||||
return $http(options).then(function(results) {
|
||||
|
@ -18,13 +18,6 @@ function (angular, _, store, config) {
|
||||
}
|
||||
}
|
||||
|
||||
this.version = config.buildInfo.version;
|
||||
this.lightTheme = false;
|
||||
this.user = new User();
|
||||
this.isSignedIn = this.user.isSignedIn;
|
||||
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
|
||||
this.sidemenu = store.getBool('grafana.sidemenu');
|
||||
|
||||
// events
|
||||
$rootScope.$on('toggle-sidemenu', function() {
|
||||
self.toggleSideMenu();
|
||||
@ -47,6 +40,12 @@ function (angular, _, store, config) {
|
||||
}, 50);
|
||||
};
|
||||
|
||||
this.version = config.buildInfo.version;
|
||||
this.lightTheme = false;
|
||||
this.user = new User();
|
||||
this.isSignedIn = this.user.isSignedIn;
|
||||
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
|
||||
this.sidemenu = store.getBool('grafana.sidemenu');
|
||||
this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
|
||||
});
|
||||
|
||||
});
|
||||
|
30
public/css/less/bootstrap-tagsinput.less
vendored
30
public/css/less/bootstrap-tagsinput.less
vendored
@ -1,33 +1,19 @@
|
||||
.bootstrap-tagsinput {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
padding: 0 0 0 6px;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
line-height: 22px;
|
||||
|
||||
background-color: @inputBackground;
|
||||
border: 1px solid @inputBorder;
|
||||
.box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
|
||||
.transition(~"border linear .2s, box-shadow linear .2s");
|
||||
|
||||
input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
margin: 0;
|
||||
width: auto !important;
|
||||
max-width: inherit;
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
border-right: 1px solid @grafanaTargetSegmentBorder;
|
||||
margin: 0px;
|
||||
border-radius: 0;
|
||||
padding: 8px 6px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@ -49,4 +35,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,8 @@
|
||||
padding-bottom: 10px;
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 18px 8px;
|
||||
padding: 8px 8px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
|
@ -185,10 +185,26 @@ define([
|
||||
expect(model.annotations.list.length).to.be(0);
|
||||
expect(model.templating.list.length).to.be(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Given editable false dashboard', function() {
|
||||
var model;
|
||||
|
||||
beforeEach(function() {
|
||||
model = _dashboardSrv.create({
|
||||
editable: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('Should set meta canEdit and canSave to false', function() {
|
||||
expect(model.meta.canSave).to.be(false);
|
||||
expect(model.meta.canEdit).to.be(false);
|
||||
});
|
||||
|
||||
it('getSaveModelClone should remove meta', function() {
|
||||
var clone = model.getSaveModelClone();
|
||||
expect(clone.meta).to.be(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -500,4 +500,4 @@
|
||||
$(function() {
|
||||
$("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
|
||||
});
|
||||
})(window.jQuery);
|
||||
})(window.jQuery);
|
||||
|
@ -16,7 +16,7 @@ module.exports = function(config) {
|
||||
{
|
||||
expand: true,
|
||||
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
|
||||
dest: '<%= pkg.name %>/',
|
||||
dest: '<%= pkg.name %>-<%= pkg.version %>/',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user