mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into query-editor-style
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,4 +1,19 @@
|
|||||||
# 3.0.0 (unrelased master branch)
|
# 3.0.0-beta2 (unreleased)
|
||||||
|
|
||||||
|
### New Features (introduces since 3.0-beta1)
|
||||||
|
* **Preferences**: Set home dashboard on user and org level, closes [#1678](https://github.com/grafana/grafana/issues/1678)
|
||||||
|
* **Preferences**: Set timezone on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1200](https://github.com/grafana/grafana/issues/1200)
|
||||||
|
* **Preferences**: Set theme on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1917](https://github.com/grafana/grafana/issues/1917)
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
* **Dashboard**: Fixed dashboard panel layout for mobile devices, fixes [#4529](https://github.com/grafana/grafana/issues/4529)
|
||||||
|
* **Table Panel**: Fixed issue with table panel sort, fixes [#4532](https://github.com/grafana/grafana/issues/4532)
|
||||||
|
* **Page Load Crash**: A Datasource with null jsonData would make Grafana fail to load page, fixes [#4536](https://github.com/grafana/grafana/issues/4536)
|
||||||
|
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4540](https://github.com/grafana/grafana/issues/4540)
|
||||||
|
* **Graph**: Fix legend in table mode with series on right-y axis, fixes [#4551](https://github.com/grafana/grafana/issues/4551), [#1145](https://github.com/grafana/grafana/issues/1145)
|
||||||
|
* **Password**: Password reset link/page did not work, fixes [#4542](https://github.com/grafana/grafana/issues/4542)
|
||||||
|
|
||||||
|
# 3.0.0-beta1 (2016-03-31)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655)
|
* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ module.exports = function (grunt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.pkg.version = grunt.option('pkgVer') || config.pkg.version;
|
config.pkg.version = grunt.option('pkgVer') || config.pkg.version;
|
||||||
|
console.log('Version', config.pkg.version);
|
||||||
|
|
||||||
// load plugins
|
// load plugins
|
||||||
require('load-grunt-tasks')(grunt);
|
require('load-grunt-tasks')(grunt);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ os: Windows Server 2012 R2
|
|||||||
clone_folder: c:\gopath\src\github.com\grafana\grafana
|
clone_folder: c:\gopath\src\github.com\grafana\grafana
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
nodejs_version: "4"
|
nodejs_version: "5"
|
||||||
GOPATH: c:\gopath
|
GOPATH: c:\gopath
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
16
build.go
16
build.go
@@ -73,8 +73,7 @@ func main() {
|
|||||||
grunt("test")
|
grunt("test")
|
||||||
|
|
||||||
case "package":
|
case "package":
|
||||||
//verifyGitRepoIsClean()
|
grunt("release", fmt.Sprintf("--pkgVer=%v-%v", linuxPackageVersion, linuxPackageIteration))
|
||||||
grunt("release")
|
|
||||||
createLinuxPackages()
|
createLinuxPackages()
|
||||||
|
|
||||||
case "pkg-rpm":
|
case "pkg-rpm":
|
||||||
@@ -100,12 +99,12 @@ func main() {
|
|||||||
func makeLatestDistCopies() {
|
func makeLatestDistCopies() {
|
||||||
rpmIteration := "-1"
|
rpmIteration := "-1"
|
||||||
if linuxPackageIteration != "" {
|
if linuxPackageIteration != "" {
|
||||||
rpmIteration = "-" + linuxPackageIteration
|
rpmIteration = linuxPackageIteration
|
||||||
}
|
}
|
||||||
|
|
||||||
runError("cp", "dist/grafana_"+version+"_amd64.deb", "dist/grafana_latest_amd64.deb")
|
runError("cp", fmt.Sprintf("dist/grafana_%v-%v_amd64.deb", linuxPackageVersion, linuxPackageIteration), "dist/grafana_latest_amd64.deb")
|
||||||
runError("cp", "dist/grafana-"+linuxPackageVersion+rpmIteration+".x86_64.rpm", "dist/grafana-latest-1.x86_64.rpm")
|
runError("cp", fmt.Sprintf("dist/grafana-%v-%v.x86_64.rpm", linuxPackageVersion, rpmIteration), "dist/grafana-latest-1.x86_64.rpm")
|
||||||
runError("cp", "dist/grafana-"+version+".linux-x64.tar.gz", "dist/grafana-latest.linux-x64.tar.gz")
|
runError("cp", fmt.Sprintf("dist/grafana-%v-%v.linux-x64.tar.gz", linuxPackageVersion, linuxPackageIteration), "dist/grafana-latest.linux-x64.tar.gz")
|
||||||
}
|
}
|
||||||
|
|
||||||
func readVersionFromPackageJson() {
|
func readVersionFromPackageJson() {
|
||||||
@@ -133,6 +132,11 @@ func readVersionFromPackageJson() {
|
|||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
linuxPackageVersion = parts[0]
|
linuxPackageVersion = parts[0]
|
||||||
linuxPackageIteration = parts[1]
|
linuxPackageIteration = parts[1]
|
||||||
|
if linuxPackageIteration != "" {
|
||||||
|
// add timestamp to iteration
|
||||||
|
linuxPackageIteration = fmt.Sprintf("%s%v", linuxPackageIteration, time.Now().Unix())
|
||||||
|
}
|
||||||
|
log.Println(fmt.Sprintf("teration %v", linuxPackageIteration))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,13 +86,12 @@ pages:
|
|||||||
- ['http_api/snapshot.md', 'API', 'Snapshot API']
|
- ['http_api/snapshot.md', 'API', 'Snapshot API']
|
||||||
- ['http_api/other.md', 'API', 'Other API']
|
- ['http_api/other.md', 'API', 'Other API']
|
||||||
|
|
||||||
- ['plugins/overview.md', 'Plugins', 'Overview']
|
- ['plugins/index.md', 'Plugins', 'Overview']
|
||||||
- ['plugins/installation.md', 'Plugins', 'Installation']
|
- ['plugins/installation.md', 'Plugins', 'Installation']
|
||||||
- ['plugins/app.md', 'Plugins', 'App plugins']
|
- ['plugins/development.md', 'Plugins', 'Development']
|
||||||
- ['plugins/datasources.md', 'Plugins', 'Datasource plugins']
|
- ['plugins/apps.md', 'Plugins', 'Apps']
|
||||||
- ['plugins/panels.md', 'Plugins', 'Panel plugins']
|
- ['plugins/datasources.md', 'Plugins', 'Datasources']
|
||||||
- ['plugins/development.md', 'Plugins', 'Plugin development']
|
- ['plugins/panels.md', 'Plugins', 'Panels']
|
||||||
- ['plugins/plugin.json.md', 'Plugins', 'Plugin json']
|
|
||||||
|
|
||||||
- ['tutorials/index.md', 'Tutorials', 'Tutorials']
|
- ['tutorials/index.md', 'Tutorials', 'Tutorials']
|
||||||
- ['tutorials/hubot_howto.md', 'Tutorials', 'How To integrate Hubot and Grafana']
|
- ['tutorials/hubot_howto.md', 'Tutorials', 'How To integrate Hubot and Grafana']
|
||||||
|
|||||||
@@ -10,14 +10,21 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
.deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
|
Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
|
||||||
|
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb)
|
||||||
|
|
||||||
## Install
|
## Install Stable
|
||||||
|
|
||||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
|
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
|
||||||
$ sudo apt-get install -y adduser libfontconfig
|
$ sudo apt-get install -y adduser libfontconfig
|
||||||
$ sudo dpkg -i grafana_2.6.0_amd64.deb
|
$ sudo dpkg -i grafana_2.6.0_amd64.deb
|
||||||
|
|
||||||
|
## Install 3.0 Beta
|
||||||
|
|
||||||
|
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb
|
||||||
|
$ sudo apt-get install -y adduser libfontconfig
|
||||||
|
$ sudo dpkg -i grafana_3.0.0-beta11459429091_amd64.deb
|
||||||
|
|
||||||
## APT Repository
|
## APT Repository
|
||||||
|
|
||||||
Add the following line to your `/etc/apt/sources.list` file.
|
Add the following line to your `/etc/apt/sources.list` file.
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
.RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
|
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
|
||||||
|
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta11459429091.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm)
|
||||||
|
|
||||||
## Install from package file
|
## Install Stable Release from package file
|
||||||
|
|
||||||
You can install Grafana using Yum directly.
|
You can install Grafana using Yum directly.
|
||||||
|
|
||||||
@@ -29,6 +30,24 @@ Or install manually using `rpm`.
|
|||||||
|
|
||||||
$ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm
|
$ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm
|
||||||
|
|
||||||
|
## Install Beta Release from package file
|
||||||
|
|
||||||
|
You can install Grafana using Yum directly.
|
||||||
|
|
||||||
|
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||||
|
|
||||||
|
Or install manually using `rpm`.
|
||||||
|
|
||||||
|
#### On CentOS / Fedora / Redhat:
|
||||||
|
|
||||||
|
$ sudo yum install initscripts fontconfig
|
||||||
|
$ sudo rpm -Uvh grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||||
|
|
||||||
|
#### On OpenSuse:
|
||||||
|
|
||||||
|
$ sudo rpm -i --nodeps grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||||
|
|
||||||
|
|
||||||
## Install via YUM Repository
|
## Install via YUM Repository
|
||||||
|
|
||||||
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
|
|||||||
|
|
||||||
Description | Download
|
Description | Download
|
||||||
------------ | -------------
|
------------ | -------------
|
||||||
Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
Stable Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
||||||
|
|
||||||
## Configure
|
## Configure
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
page_title: App plugin
|
|
||||||
page_description: App plugin for Grafana
|
|
||||||
page_keywords: grafana, plugins, documentation
|
|
||||||
---
|
|
||||||
|
|
||||||
> Our goal is not to have a very extensive documentation but rather have actual code that people can look at. An example implementation of an app can be found in this [example app repo](https://github.com/grafana/example-app)
|
|
||||||
|
|
||||||
# Apps
|
|
||||||
|
|
||||||
App plugins is a new kind of grafana plugin that can bundle datasource and panel plugins within one package. It also enable the plugin author to create custom pages within grafana. The custom pages enables the plugin author to include things like documentation, sign up forms or controlling other services using HTTP requests.
|
|
||||||
|
|
||||||
Datasource and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
|
|
||||||
|
|
||||||
<img class="no-shadow" src="/img/v3/app-in-main-menu.png">
|
|
||||||
|
|
||||||
## Enabling app plugins
|
|
||||||
After installing an app it have to be enabled before it show up as an datasource or panel. You can do that on the app page in the config tab.
|
|
||||||
|
|
||||||
## README.md
|
|
||||||
|
|
||||||
The readme file in the mounted folder will show up in the overview tab on the app page.
|
|
||||||
|
|
||||||
## Module exports
|
|
||||||
```javascript
|
|
||||||
export {
|
|
||||||
ExampleAppConfigCtrl as ConfigCtrl,
|
|
||||||
StreamPageCtrl,
|
|
||||||
LogsPageCtrl
|
|
||||||
};
|
|
||||||
```
|
|
||||||
The only required export is the ConfigCtrl. Both StreamPageCtrl and LogsPageCtrl are custom pages defined in plugin.json
|
|
||||||
|
|
||||||
## Custom pages
|
|
||||||
Custom pages are defined in the plugin.json like this.
|
|
||||||
```json
|
|
||||||
"pages": [
|
|
||||||
{ "name": "Live stream", "component": "StreamPageCtrl", "role": "Editor"},
|
|
||||||
{ "name": "Log view", "component": "LogsPageCtrl", "role": "Viewer"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
The component field have to match one of the components exported in the module.js in the root of the plugin.
|
|
||||||
|
|
||||||
## Bundled plugins
|
|
||||||
|
|
||||||
When Grafana starts it will scan all directories within an app plugin and load folders containing a plugin.json as an plugin.
|
|
||||||
24
docs/sources/plugins/apps.md
Normal file
24
docs/sources/plugins/apps.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
page_title: App plugin
|
||||||
|
page_description: App plugin for Grafana
|
||||||
|
page_keywords: grafana, plugins, documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# Apps
|
||||||
|
|
||||||
|
App plugins is a new kind of grafana plugin that can bundle datasource and panel plugins within one package. It also enable the plugin author to create custom pages within grafana. The custom pages enables the plugin author to include things like documentation, sign up forms or controlling other services using HTTP requests.
|
||||||
|
|
||||||
|
Datasource and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
|
||||||
|
|
||||||
|
<img class="no-shadow" src="/img/v3/app-in-main-menu.png">
|
||||||
|
|
||||||
|
## Enabling app plugins
|
||||||
|
After installing an app it have to be enabled before it show up as an datasource or panel. You can do that on the app page in the config tab.
|
||||||
|
|
||||||
|
### Develop your own App
|
||||||
|
|
||||||
|
> Our goal is not to have a very extensive documentation but rather have actual
|
||||||
|
> code that people can look at. An example implementation of an app can be found
|
||||||
|
> in this [example app repo](https://github.com/grafana/example-app)
|
||||||
|
|
||||||
@@ -4,11 +4,18 @@ page_description: Datasource plugins for Grafana
|
|||||||
page_keywords: grafana, plugins, documentation
|
page_keywords: grafana, plugins, documentation
|
||||||
---
|
---
|
||||||
|
|
||||||
> Our goal is not to have a very extensive documentation but rather have actual code that people can look at. An example implementation of a datasource can be found in this [example datasource repo](https://github.com/grafana/simple-json-datasource)
|
|
||||||
|
|
||||||
# Datasources
|
# Datasources
|
||||||
|
|
||||||
Datasource plugins enables people to develop plugins for any database that communicates over http. Its up to the plugin to transform the data into time series data so that any grafana panel can then show it.
|
Datasource plugins enables people to develop plugins for any database that
|
||||||
|
communicates over http. Its up to the plugin to transform the data into
|
||||||
|
time series data so that any grafana panel can then show it.
|
||||||
|
|
||||||
|
## Datasource development
|
||||||
|
|
||||||
|
> Our goal is not to have a very extensive documentation but rather have actual
|
||||||
|
> code that people can look at. An example implementation of a datasource can be
|
||||||
|
> found in this [example datasource repo](https://github.com/grafana/simple-json-datasource)
|
||||||
|
|
||||||
To interact with the rest of grafana the plugins module file can export 5 different components.
|
To interact with the rest of grafana the plugins module file can export 5 different components.
|
||||||
|
|
||||||
@@ -19,11 +26,14 @@ To interact with the rest of grafana the plugins module file can export 5 differ
|
|||||||
- AnnotationsQueryCtrl
|
- AnnotationsQueryCtrl
|
||||||
|
|
||||||
## Plugin json
|
## Plugin json
|
||||||
|
|
||||||
There are two datasource specific settings for the plugin.json
|
There are two datasource specific settings for the plugin.json
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"annotations": false,
|
"annotations": false,
|
||||||
```
|
```
|
||||||
|
|
||||||
These settings indicates what kind of data the plugin can deliver. At least one of them have to be true
|
These settings indicates what kind of data the plugin can deliver. At least one of them have to be true
|
||||||
|
|
||||||
## Datasource
|
## Datasource
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
page_title: Plugin development
|
page_title: Plugin development guide
|
||||||
page_description: Plugin development for Grafana
|
page_description: Plugin development for Grafana
|
||||||
page_keywords: grafana, plugins, documentation, development
|
page_keywords: grafana, plugins, documentation, development
|
||||||
---
|
---
|
||||||
@@ -11,7 +11,7 @@ From grafana 3.0 it's very easy to develop your own plugins and share them with
|
|||||||
## Short version
|
## Short version
|
||||||
|
|
||||||
1. [Setup grafana](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md)
|
1. [Setup grafana](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md)
|
||||||
2. Clone an example plugin into ```data/plugins```
|
2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if your running development version from source dir)
|
||||||
3. Code away!
|
3. Code away!
|
||||||
|
|
||||||
## What languages?
|
## What languages?
|
||||||
@@ -26,20 +26,25 @@ All our example plugins have build scripted configured.
|
|||||||
|
|
||||||
## module.(js|ts)
|
## module.(js|ts)
|
||||||
|
|
||||||
This is the entry point for every plugin. This is the place where you should export your plugin implementation. Depending on what kind of plugin you are developing you will be expected to export different things. You can find whats expected for [datasource](http://docs.grafana.org/v3.0/plugins/datasources/), [panels](http://docs.grafana.org/v3.0/plugins/panels/) and [apps](http://docs.grafana.org/v3.0/plugins/app/)
|
This is the entry point for every plugin. This is the place where you should export
|
||||||
plugins in the documentation.
|
your plugin implementation. Depending on what kind of plugin you are developing you
|
||||||
|
will be expected to export different things. You can find what's expected for [datasource](./datasources.md), [panels](./panels.md)
|
||||||
|
and [apps](./apps.md) plugins in the documentation.
|
||||||
|
|
||||||
## Start developing your plugin
|
## Start developing your plugin
|
||||||
There are two ways that you can start developing a Grafana plugin.
|
There are two ways that you can start developing a Grafana plugin.
|
||||||
1. Setup a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md) and place your plugin in the ```data/plugins``` folder.
|
|
||||||
2. Install Grafana and place your plugin the plugins directory which is set in your [config file](http://docs.grafana.org/installation/configuration/)
|
1. Setup a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md) and place your plugin in the ```data/plugins``` folder.
|
||||||
|
2. Install Grafana and place your plugin in the plugins directory which is set in your [config file](../installation/configuration.md). By default this is `/var/lib/grafana/plugins` on Linux systems.
|
||||||
|
3. Place your plugin directory anywhere you like and specify it grafana.ini.
|
||||||
|
|
||||||
We encourage people to setup the full Grafana environment so that you can get inspiration from the rest of grafana code base.
|
We encourage people to setup the full Grafana environment so that you can get inspiration from the rest of grafana code base.
|
||||||
|
|
||||||
When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless the folder contains a subfolder named dist. In that case grafana will mount the dist folder instead.
|
When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless
|
||||||
This makes it possible to have both built and src content in the same plugin folder.
|
the folder contains a subfolder named dist. In that case grafana will mount the dist folder instead.
|
||||||
|
This makes it possible to have both built and src content in the same plugin git repo.
|
||||||
|
|
||||||
## Boilerplate
|
## Examples
|
||||||
We currently have three different examples that you can fork/download to get started developing your grafana plugin.
|
We currently have three different examples that you can fork/download to get started developing your grafana plugin.
|
||||||
|
|
||||||
- [simple-json-datasource](https://github.com/grafana/simple-json-datasource) (small datasource plugin for querying json data from backends)
|
- [simple-json-datasource](https://github.com/grafana/simple-json-datasource) (small datasource plugin for querying json data from backends)
|
||||||
21
docs/sources/plugins/index.md
Normal file
21
docs/sources/plugins/index.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
page_title: Plugin overview
|
||||||
|
page_description: Plugins for Grafana
|
||||||
|
page_keywords: grafana, plugins, documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
|
||||||
|
From Grafana 3.0 not only datasource plugins are supported but also panel plugins and apps.
|
||||||
|
Having panels as plugins make it easy to create and add any kind of panel, to show your data
|
||||||
|
or improve your favorite dashboards. Apps is something new in Grafana that enables
|
||||||
|
bundling of datasources, panels, dashboards and Grafana pages into a cohesive experiance.
|
||||||
|
|
||||||
|
Grafana already have a strong community of contributors and plugin developers.
|
||||||
|
By making it easier to develop and install plugins we hope that the community
|
||||||
|
can grow even stronger and develop new plugins that we would never think about.
|
||||||
|
|
||||||
|
You can discover available plugins on [Grafana.net](http://grafana.net)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -4,14 +4,12 @@ page_description: Plugin installation for Grafana
|
|||||||
page_keywords: grafana, plugins, documentation
|
page_keywords: grafana, plugins, documentation
|
||||||
---
|
---
|
||||||
|
|
||||||
# Plugins
|
# Installing plugins
|
||||||
|
|
||||||
## Installing plugins
|
|
||||||
|
|
||||||
The easiest way to install plugins is by using the CLI tool grafana-cli which is bundled with grafana. Before any modification take place after modifying plugins, grafana-server needs to be restarted.
|
The easiest way to install plugins is by using the CLI tool grafana-cli which is bundled with grafana. Before any modification take place after modifying plugins, grafana-server needs to be restarted.
|
||||||
|
|
||||||
### Grafana plugin directory
|
### Grafana plugin directory
|
||||||
On Linux systems the grafana-cli will assume that the grafana plugin directory is "/var/lib/grafana/plugins". It's possible to override the directory which grafana-cli will operate on by specifying the --path flag. On Windows systems this parameter have to be specified for every call.
|
On Linux systems the grafana-cli will assume that the grafana plugin directory is `/var/lib/grafana/plugins`. It's possible to override the directory which grafana-cli will operate on by specifying the --path flag. On Windows systems this parameter have to be specified for every call.
|
||||||
|
|
||||||
### Grafana-cli commands
|
### Grafana-cli commands
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
page_title: Plugin overview
|
|
||||||
page_description: Plugins for Grafana
|
|
||||||
page_keywords: grafana, plugins, documentation
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plugins
|
|
||||||
|
|
||||||
From Grafana 3.0 not only datasource plugins are supported but also panel plugins and apps. Having panels as plugins make it easy to create and add any kind of panel, to show your data or improve your favorite dashboards. Apps is something new in Grafana that enables bundling of datasources, panels that belongs together.
|
|
||||||
|
|
||||||
Grafana already have a strong community of contributors and plugin developers. By making it easier to develop and install plugins we hope that the community can grow even stronger and develop new plugins that we would never think about.
|
|
||||||
|
|
||||||
@@ -4,26 +4,15 @@ page_description: Panel plugins for Grafana
|
|||||||
page_keywords: grafana, plugins, documentation
|
page_keywords: grafana, plugins, documentation
|
||||||
---
|
---
|
||||||
|
|
||||||
> Our goal is not to have a very extensive documentation but rather have actual code that people can look at. An example implementation of a datasource can be found in the grafana repo under /examples/panel-boilerplate-es5
|
|
||||||
|
|
||||||
# Panels
|
# Panels
|
||||||
|
|
||||||
To interact with the rest of grafana the panel plugin need to export a class in the module.js.
|
Panels are the main bulding block of dashboards.
|
||||||
This class have to inherit from sdk.PanelCtrl or sdk.MetricsPanelCtrl and be exported as PanelCtrl.
|
|
||||||
|
|
||||||
```javascript
|
## Panel development
|
||||||
return {
|
|
||||||
PanelCtrl: BoilerPlatePanelCtrl
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This class will be instantiated once for every panel of its kind in a dashboard and treated as an AngularJs controller.
|
Examples
|
||||||
|
|
||||||
## MetricsPanelCtrl or PanelCtrl
|
- [clock-panel](https://github.com/grafana/clock-panel)
|
||||||
|
- [singlestat-panel](https://github.com/grafana/grafana/blob/master/public/app/plugins/panel/singlestat/module.ts)
|
||||||
MetricsPanelCtrl inherits from PanelCtrl and adds some common features for datasource usage. So if your Panel will be working with a datasource you should inherit from MetricsPanelCtrl. If don't need to access any datasource then you should inherit from PanelCtrl instead.
|
|
||||||
|
|
||||||
## Implementing a MetricsPanelCtrl
|
|
||||||
|
|
||||||
If you choose to inherit from MetricsPanelCtrl you should implement a function called refreshData that will take a datasource as in parameter when its time to get new data. Its recommended that the refreshData function calls the issueQueries in the base class but its not mandatory. An examples of such implementation can be found in our [example panel](https://github.com/grafana/grafana/blob/master/examples/panel-boilerplate-es5/module.js#L27-L38)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<li><a class='version' href='/v3.0'>Version v3.0</a></li>
|
||||||
<li><a class='version' href='/v2.6'>Version v2.6</a></li>
|
<li><a class='version' href='/v2.6'>Version v2.6</a></li>
|
||||||
<li><a class='version' href='/v2.5'>Version v2.5</a></li>
|
<li><a class='version' href='/v2.5'>Version v2.5</a></li>
|
||||||
<li><a class='version' href='/v2.1'>Version v2.1</a></li>
|
<li><a class='version' href='/v2.1'>Version v2.1</a></li>
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ module.exports = function(config) {
|
|||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: [
|
files: [
|
||||||
'vendor/npm/es5-shim/es5-shim.js',
|
|
||||||
'vendor/npm/es5-shim/es5-sham.js',
|
|
||||||
'vendor/npm/es6-shim/es6-shim.js',
|
'vendor/npm/es6-shim/es6-shim.js',
|
||||||
'vendor/npm/es6-promise/dist/es6-promise.js',
|
|
||||||
'vendor/npm/systemjs/dist/system.src.js',
|
'vendor/npm/systemjs/dist/system.src.js',
|
||||||
'test/test-main.js',
|
'test/test-main.js',
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
"company": "Coding Instinct AB"
|
"company": "Coding Instinct AB"
|
||||||
},
|
},
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "3.0.0-pre1",
|
"version": "3.0.0-beta2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular2": "2.0.0-beta.12",
|
"angular2": "2.0.0-beta.12",
|
||||||
|
"zone.js": "^0.6.6",
|
||||||
"autoprefixer": "^6.3.3",
|
"autoprefixer": "^6.3.3",
|
||||||
"es6-promise": "^3.0.2",
|
"es6-promise": "^3.0.2",
|
||||||
"es6-shim": "^0.35.0",
|
"es6-shim": "^0.35.0",
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^0.6.1",
|
||||||
"grunt-filerev": "^0.2.1",
|
"grunt-filerev": "^0.2.1",
|
||||||
"grunt-git-describe": "~2.3.2",
|
"grunt-git-describe": "~2.3.2",
|
||||||
"grunt-karma": "~0.12.1",
|
"grunt-karma": "~0.12.2",
|
||||||
"grunt-ng-annotate": "^1.0.1",
|
"grunt-ng-annotate": "^1.0.1",
|
||||||
"grunt-notify": "^0.4.3",
|
"grunt-notify": "^0.4.3",
|
||||||
"grunt-postcss": "^0.8.0",
|
"grunt-postcss": "^0.8.0",
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
"grunt-typescript": "^0.8.0",
|
"grunt-typescript": "^0.8.0",
|
||||||
"grunt-usemin": "3.0.0",
|
"grunt-usemin": "3.0.0",
|
||||||
"jshint-stylish": "~2.1.0",
|
"jshint-stylish": "~2.1.0",
|
||||||
"karma": "~0.13.15",
|
"karma": "0.13.22",
|
||||||
"karma-chrome-launcher": "~0.2.2",
|
"karma-chrome-launcher": "~0.2.2",
|
||||||
"karma-coverage": "0.5.3",
|
"karma-coverage": "0.5.3",
|
||||||
"karma-coveralls": "1.1.2",
|
"karma-coveralls": "1.1.2",
|
||||||
@@ -67,7 +68,6 @@
|
|||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es5-shim": "^4.4.1",
|
|
||||||
"grunt-jscs": "~1.5.x",
|
"grunt-jscs": "~1.5.x",
|
||||||
"grunt-sass-lint": "^0.1.0",
|
"grunt-sass-lint": "^0.1.0",
|
||||||
"grunt-sync": "^0.4.1",
|
"grunt-sync": "^0.4.1",
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
version=2.6.0
|
deb_ver=3.0.0-beta11459429091
|
||||||
|
rpm_ver=3.0.0-beta11459429091
|
||||||
|
|
||||||
wget https://grafanarel.s3.amazonaws.com/builds/grafana_${version}_amd64.deb
|
#rpm_ver=3.0.0-1
|
||||||
|
|
||||||
package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb
|
wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
|
||||||
package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb
|
|
||||||
package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb
|
|
||||||
package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb
|
|
||||||
|
|
||||||
wget https://grafanarel.s3.amazonaws.com/builds/grafana-${version}-1.x86_64.rpm
|
# package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb
|
||||||
|
# package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb
|
||||||
|
|
||||||
package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm
|
package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
|
||||||
package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm
|
package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
|
||||||
package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
|
|
||||||
package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm
|
wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
|
||||||
|
|
||||||
|
package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
|
||||||
|
package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
|
||||||
|
|
||||||
|
# package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
|
||||||
|
# package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
|
|
||||||
// authed views
|
// authed views
|
||||||
r.Get("/profile/", reqSignedIn, Index)
|
r.Get("/profile/", reqSignedIn, Index)
|
||||||
|
r.Get("/profile/password", reqSignedIn, Index)
|
||||||
r.Get("/org/", reqSignedIn, Index)
|
r.Get("/org/", reqSignedIn, Index)
|
||||||
r.Get("/org/new", reqSignedIn, Index)
|
r.Get("/org/new", reqSignedIn, Index)
|
||||||
r.Get("/datasources/", reqSignedIn, Index)
|
r.Get("/datasources/", reqSignedIn, Index)
|
||||||
@@ -96,10 +97,15 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||||
r.Post("/using/:id", wrap(UserSetUsingOrg))
|
r.Post("/using/:id", wrap(UserSetUsingOrg))
|
||||||
r.Get("/orgs", wrap(GetSignedInUserOrgList))
|
r.Get("/orgs", wrap(GetSignedInUserOrgList))
|
||||||
|
|
||||||
r.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
r.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
||||||
r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
||||||
|
|
||||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
||||||
r.Get("/quotas", wrap(GetUserQuotas))
|
r.Get("/quotas", wrap(GetUserQuotas))
|
||||||
|
|
||||||
|
r.Get("/preferences", wrap(GetUserPreferences))
|
||||||
|
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
|
||||||
})
|
})
|
||||||
|
|
||||||
// users (admin permission required)
|
// users (admin permission required)
|
||||||
@@ -130,6 +136,9 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||||
|
|
||||||
|
// prefs
|
||||||
|
r.Get("/preferences", wrap(GetOrgPreferences))
|
||||||
|
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||||
}, reqOrgAdmin)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
// create new org
|
// create new org
|
||||||
@@ -164,6 +173,11 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Delete("/:id", wrap(DeleteApiKey))
|
r.Delete("/:id", wrap(DeleteApiKey))
|
||||||
}, reqOrgAdmin)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
r.Group("/preferences", func() {
|
||||||
|
r.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
|
||||||
|
})
|
||||||
|
|
||||||
// Data sources
|
// Data sources
|
||||||
r.Group("/datasources", func() {
|
r.Group("/datasources", func() {
|
||||||
r.Get("/", GetDataSources)
|
r.Get("/", GetDataSources)
|
||||||
|
|||||||
@@ -159,6 +159,24 @@ func canEditDashboard(role m.RoleType) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetHomeDashboard(c *middleware.Context) {
|
func GetHomeDashboard(c *middleware.Context) {
|
||||||
|
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||||
|
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||||
|
c.JsonApiErr(500, "Failed to get preferences", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefsQuery.Result.HomeDashboardId != 0 {
|
||||||
|
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
|
||||||
|
err := bus.Dispatch(&slugQuery)
|
||||||
|
if err != nil {
|
||||||
|
c.JsonApiErr(500, "Failed to get slug from database", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
|
||||||
|
c.JSON(200, &dashRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
|
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type CurrentUser struct {
|
|||||||
OrgRole m.RoleType `json:"orgRole"`
|
OrgRole m.RoleType `json:"orgRole"`
|
||||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||||
GravatarUrl string `json:"gravatarUrl"`
|
GravatarUrl string `json:"gravatarUrl"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardMeta struct {
|
type DashboardMeta struct {
|
||||||
@@ -57,6 +58,10 @@ type DashboardFullWithMeta struct {
|
|||||||
Dashboard *simplejson.Json `json:"dashboard"`
|
Dashboard *simplejson.Json `json:"dashboard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DashboardRedirect struct {
|
||||||
|
RedirectUri string `json:"redirectUri"`
|
||||||
|
}
|
||||||
|
|
||||||
type DataSource struct {
|
type DataSource struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
OrgId int64 `json:"orgId"`
|
OrgId int64 `json:"orgId"`
|
||||||
|
|||||||
13
pkg/api/dtos/prefs.go
Normal file
13
pkg/api/dtos/prefs.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package dtos
|
||||||
|
|
||||||
|
type Prefs struct {
|
||||||
|
Theme string `json:"theme"`
|
||||||
|
HomeDashboardId int64 `json:"homeDashboardId"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePrefsCmd struct {
|
||||||
|
Theme string `json:"theme"`
|
||||||
|
HomeDashboardId int64 `json:"homeDashboardId"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
defaultDatasource = ds.Name
|
defaultDatasource = ds.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ds.JsonData.MustMap()) > 0 {
|
if ds.JsonData != nil {
|
||||||
dsMap["jsonData"] = ds.JsonData
|
dsMap["jsonData"] = ds.JsonData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
@@ -14,6 +15,12 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||||
|
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prefs := prefsQuery.Result
|
||||||
|
|
||||||
var data = dtos.IndexViewData{
|
var data = dtos.IndexViewData{
|
||||||
User: &dtos.CurrentUser{
|
User: &dtos.CurrentUser{
|
||||||
Id: c.UserId,
|
Id: c.UserId,
|
||||||
@@ -21,12 +28,13 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
Login: c.Login,
|
Login: c.Login,
|
||||||
Email: c.Email,
|
Email: c.Email,
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
LightTheme: c.Theme == "light",
|
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
OrgName: c.OrgName,
|
OrgName: c.OrgName,
|
||||||
OrgRole: c.OrgRole,
|
OrgRole: c.OrgRole,
|
||||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||||
|
LightTheme: prefs.Theme == "light",
|
||||||
|
Timezone: prefs.Timezone,
|
||||||
},
|
},
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
AppUrl: setting.AppUrl,
|
AppUrl: setting.AppUrl,
|
||||||
|
|||||||
73
pkg/api/preferences.go
Normal file
73
pkg/api/preferences.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// POST /api/preferences/set-home-dash
|
||||||
|
func SetHomeDashboard(c *middleware.Context, cmd m.SavePreferencesCommand) Response {
|
||||||
|
|
||||||
|
cmd.UserId = c.UserId
|
||||||
|
cmd.OrgId = c.OrgId
|
||||||
|
|
||||||
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
return ApiError(500, "Failed to set home dashboard", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiSuccess("Home dashboard set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/user/preferences
|
||||||
|
func GetUserPreferences(c *middleware.Context) Response {
|
||||||
|
return getPreferencesFor(c.OrgId, c.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPreferencesFor(orgId int64, userId int64) Response {
|
||||||
|
prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId}
|
||||||
|
|
||||||
|
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||||
|
return ApiError(500, "Failed to get preferences", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := dtos.Prefs{
|
||||||
|
Theme: prefsQuery.Result.Theme,
|
||||||
|
HomeDashboardId: prefsQuery.Result.HomeDashboardId,
|
||||||
|
Timezone: prefsQuery.Result.Timezone,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(200, &dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT /api/user/preferences
|
||||||
|
func UpdateUserPreferences(c *middleware.Context, dtoCmd dtos.UpdatePrefsCmd) Response {
|
||||||
|
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
|
||||||
|
saveCmd := m.SavePreferencesCommand{
|
||||||
|
UserId: userId,
|
||||||
|
OrgId: orgId,
|
||||||
|
Theme: dtoCmd.Theme,
|
||||||
|
Timezone: dtoCmd.Timezone,
|
||||||
|
HomeDashboardId: dtoCmd.HomeDashboardId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bus.Dispatch(&saveCmd); err != nil {
|
||||||
|
return ApiError(500, "Failed to save preferences", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiSuccess("Preferences updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/org/preferences
|
||||||
|
func GetOrgPreferences(c *middleware.Context) Response {
|
||||||
|
return getPreferencesFor(c.OrgId, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT /api/org/preferences
|
||||||
|
func UpdateOrgPreferences(c *middleware.Context, dtoCmd dtos.UpdatePrefsCmd) Response {
|
||||||
|
return updatePreferencesFor(c.OrgId, 0, &dtoCmd)
|
||||||
|
}
|
||||||
@@ -148,3 +148,8 @@ type GetDashboardsQuery struct {
|
|||||||
DashboardIds []int64
|
DashboardIds []int64
|
||||||
Result *[]Dashboard
|
Result *[]Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetDashboardSlugByIdQuery struct {
|
||||||
|
Id int64
|
||||||
|
Result string
|
||||||
|
}
|
||||||
|
|||||||
53
pkg/models/preferences.go
Normal file
53
pkg/models/preferences.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Typed errors
|
||||||
|
var (
|
||||||
|
ErrPreferencesNotFound = errors.New("Preferences not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Preferences struct {
|
||||||
|
Id int64
|
||||||
|
OrgId int64
|
||||||
|
UserId int64
|
||||||
|
Version int
|
||||||
|
HomeDashboardId int64
|
||||||
|
Timezone string
|
||||||
|
Theme string
|
||||||
|
Created time.Time
|
||||||
|
Updated time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------
|
||||||
|
// QUERIES
|
||||||
|
|
||||||
|
type GetPreferencesQuery struct {
|
||||||
|
Id int64
|
||||||
|
OrgId int64
|
||||||
|
UserId int64
|
||||||
|
|
||||||
|
Result *Preferences
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPreferencesWithDefaultsQuery struct {
|
||||||
|
Id int64
|
||||||
|
OrgId int64
|
||||||
|
UserId int64
|
||||||
|
|
||||||
|
Result *Preferences
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------
|
||||||
|
// COMMANDS
|
||||||
|
type SavePreferencesCommand struct {
|
||||||
|
UserId int64
|
||||||
|
OrgId int64
|
||||||
|
|
||||||
|
HomeDashboardId int64 `json:"homeDashboardId"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
Theme string `json:"theme"`
|
||||||
|
}
|
||||||
@@ -136,7 +136,6 @@ type SignedInUser struct {
|
|||||||
Login string
|
Login string
|
||||||
Name string
|
Name string
|
||||||
Email string
|
Email string
|
||||||
Theme string
|
|
||||||
ApiKeyId int64
|
ApiKeyId int64
|
||||||
IsGrafanaAdmin bool
|
IsGrafanaAdmin bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ func init() {
|
|||||||
bus.AddHandler("sql", DeleteDashboard)
|
bus.AddHandler("sql", DeleteDashboard)
|
||||||
bus.AddHandler("sql", SearchDashboards)
|
bus.AddHandler("sql", SearchDashboards)
|
||||||
bus.AddHandler("sql", GetDashboardTags)
|
bus.AddHandler("sql", GetDashboardTags)
|
||||||
|
bus.AddHandler("sql", GetDashboardSlugById)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||||
@@ -255,3 +256,23 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DashboardSlugDTO struct {
|
||||||
|
Slug string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
|
||||||
|
var rawSql = `SELECT slug from dashboard WHERE Id=?`
|
||||||
|
var slug = DashboardSlugDTO{}
|
||||||
|
|
||||||
|
exists, err := x.Sql(rawSql, query.Id).Get(&slug)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if exists == false {
|
||||||
|
return m.ErrDashboardNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Result = slug.Slug
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,17 +4,27 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|||||||
|
|
||||||
func addPreferencesMigrations(mg *Migrator) {
|
func addPreferencesMigrations(mg *Migrator) {
|
||||||
|
|
||||||
preferencesV1 := Table{
|
mg.AddMigration("drop preferences table v2", NewDropTableMigration("preferences"))
|
||||||
|
|
||||||
|
preferencesV2 := Table{
|
||||||
Name: "preferences",
|
Name: "preferences",
|
||||||
Columns: []*Column{
|
Columns: []*Column{
|
||||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||||
{Name: "pref_id", Type: DB_Int, Nullable: false},
|
{Name: "org_id", Type: DB_Int, Nullable: true},
|
||||||
{Name: "pref_type", Type: DB_NVarchar, Length: 255, Nullable: false},
|
{Name: "user_id", Type: DB_NVarchar, Length: 255, Nullable: true},
|
||||||
{Name: "pref_data", Type: DB_Text, Nullable: false},
|
{Name: "version", Type: DB_Int, Nullable: false},
|
||||||
|
{Name: "home_dashboard_id", Type: DB_BigInt, Nullable: true},
|
||||||
|
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: true},
|
||||||
|
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: true},
|
||||||
|
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||||
|
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||||
|
},
|
||||||
|
Indices: []*Index{
|
||||||
|
{Cols: []string{"org_id"}},
|
||||||
|
{Cols: []string{"user_id"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// create table
|
// create table
|
||||||
mg.AddMigration("create preferences table v1", NewAddTableMigration(preferencesV1))
|
mg.AddMigration("create preferences table v2", NewAddTableMigration(preferencesV2))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
pkg/services/sqlstore/preferences.go
Normal file
96
pkg/services/sqlstore/preferences.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bus.AddHandler("sql", GetPreferences)
|
||||||
|
bus.AddHandler("sql", GetPreferencesWithDefaults)
|
||||||
|
bus.AddHandler("sql", SavePreferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error {
|
||||||
|
|
||||||
|
prefs := make([]*m.Preferences, 0)
|
||||||
|
filter := "(org_id=? AND user_id=?) OR (org_id=? AND user_id=0)"
|
||||||
|
err := x.Where(filter, query.OrgId, query.UserId, query.OrgId).
|
||||||
|
OrderBy("user_id ASC").
|
||||||
|
Find(&prefs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &m.Preferences{
|
||||||
|
Theme: "dark",
|
||||||
|
Timezone: "browser",
|
||||||
|
HomeDashboardId: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range prefs {
|
||||||
|
if p.Theme != "" {
|
||||||
|
res.Theme = p.Theme
|
||||||
|
}
|
||||||
|
if p.Timezone != "" {
|
||||||
|
res.Timezone = p.Timezone
|
||||||
|
}
|
||||||
|
if p.HomeDashboardId != 0 {
|
||||||
|
res.HomeDashboardId = p.HomeDashboardId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Result = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPreferences(query *m.GetPreferencesQuery) error {
|
||||||
|
|
||||||
|
var prefs m.Preferences
|
||||||
|
exists, err := x.Where("org_id=? AND user_id=?", query.OrgId, query.UserId).Get(&prefs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
query.Result = &prefs
|
||||||
|
} else {
|
||||||
|
query.Result = new(m.Preferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SavePreferences(cmd *m.SavePreferencesCommand) error {
|
||||||
|
return inTransaction2(func(sess *session) error {
|
||||||
|
|
||||||
|
var prefs m.Preferences
|
||||||
|
exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
prefs = m.Preferences{
|
||||||
|
UserId: cmd.UserId,
|
||||||
|
OrgId: cmd.OrgId,
|
||||||
|
HomeDashboardId: cmd.HomeDashboardId,
|
||||||
|
Timezone: cmd.Timezone,
|
||||||
|
Theme: cmd.Theme,
|
||||||
|
Created: time.Now(),
|
||||||
|
Updated: time.Now(),
|
||||||
|
}
|
||||||
|
_, err = sess.Insert(&prefs)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
prefs.HomeDashboardId = cmd.HomeDashboardId
|
||||||
|
prefs.Timezone = cmd.Timezone
|
||||||
|
prefs.Theme = cmd.Theme
|
||||||
|
prefs.Updated = time.Now()
|
||||||
|
prefs.Version += 1
|
||||||
|
_, err := sess.Id(prefs.Id).AllCols().Update(&prefs)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -273,7 +273,6 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
|
|||||||
u.email as email,
|
u.email as email,
|
||||||
u.login as login,
|
u.login as login,
|
||||||
u.name as name,
|
u.name as name,
|
||||||
u.theme as theme,
|
|
||||||
org.name as org_name,
|
org.name as org_name,
|
||||||
org_user.role as org_role,
|
org_user.role as org_role,
|
||||||
org.id as org_id
|
org.id as org_id
|
||||||
|
|||||||
44
public/app/core/components/dashboard_selector.ts
Normal file
44
public/app/core/components/dashboard_selector.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
///<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 = `
|
||||||
|
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class DashboardSelectorCtrl {
|
||||||
|
model: any;
|
||||||
|
options: any;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
constructor(private backendSrv) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.options = [{value: 0, text: 'Default'}];
|
||||||
|
|
||||||
|
return this.backendSrv.search({starred: true}).then(res => {
|
||||||
|
res.forEach(dash => {
|
||||||
|
this.options.push({value: dash.id, text: dash.title});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dashboardSelector() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
controller: DashboardSelectorCtrl,
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: 'ctrl',
|
||||||
|
template: template,
|
||||||
|
scope: {
|
||||||
|
model: '='
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.directive('dashboardSelector', dashboardSelector);
|
||||||
@@ -32,6 +32,7 @@ import {liveSrv} from './live/live_srv';
|
|||||||
import {Emitter} from './utils/emitter';
|
import {Emitter} from './utils/emitter';
|
||||||
import {layoutSelector} from './components/layout_selector/layout_selector';
|
import {layoutSelector} from './components/layout_selector/layout_selector';
|
||||||
import {switchDirective} from './components/switch';
|
import {switchDirective} from './components/switch';
|
||||||
|
import {dashboardSelector} from './components/dashboard_selector';
|
||||||
import 'app/core/controllers/all';
|
import 'app/core/controllers/all';
|
||||||
import 'app/core/services/all';
|
import 'app/core/services/all';
|
||||||
import 'app/core/routes/routes';
|
import 'app/core/routes/routes';
|
||||||
@@ -54,4 +55,5 @@ export {
|
|||||||
infoPopover,
|
infoPopover,
|
||||||
Emitter,
|
Emitter,
|
||||||
appEvents,
|
appEvents,
|
||||||
|
dashboardSelector,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ define([
|
|||||||
function (coreModule) {
|
function (coreModule) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv) {
|
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
|
||||||
|
|
||||||
if (!$routeParams.slug) {
|
if (!$routeParams.slug) {
|
||||||
backendSrv.get('/api/dashboards/home').then(function(result) {
|
backendSrv.get('/api/dashboards/home').then(function(homeDash) {
|
||||||
var meta = result.meta;
|
if (homeDash.redirectUri) {
|
||||||
meta.canSave = meta.canShare = meta.canStar = false;
|
$location.path('dashboard/' + homeDash.redirectUri);
|
||||||
$scope.initDashboard(result, $scope);
|
} else {
|
||||||
|
var meta = homeDash.meta;
|
||||||
|
meta.canSave = meta.canShare = meta.canStar = false;
|
||||||
|
$scope.initDashboard(homeDash, $scope);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,16 +88,20 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/profile', {
|
.when('/profile', {
|
||||||
templateUrl: 'public/app/features/profile/partials/profile.html',
|
templateUrl: 'public/app/features/org/partials/profile.html',
|
||||||
controller : 'ProfileCtrl',
|
controller : 'ProfileCtrl',
|
||||||
|
controllerAs: 'ctrl',
|
||||||
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/profile/password', {
|
.when('/profile/password', {
|
||||||
templateUrl: 'public/app/features/profile/partials/password.html',
|
templateUrl: 'public/app/features/org/partials/change_password.html',
|
||||||
controller : 'ChangePasswordCtrl',
|
controller : 'ChangePasswordCtrl',
|
||||||
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/profile/select-org', {
|
.when('/profile/select-org', {
|
||||||
templateUrl: 'public/app/features/profile/partials/select_org.html',
|
templateUrl: 'public/app/features/org/partials/select_org.html',
|
||||||
controller : 'SelectOrgCtrl',
|
controller : 'SelectOrgCtrl',
|
||||||
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
// ADMIN
|
// ADMIN
|
||||||
.when('/admin', {
|
.when('/admin', {
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
define([
|
|
||||||
'angular',
|
|
||||||
'lodash',
|
|
||||||
'../core_module',
|
|
||||||
'app/core/store',
|
|
||||||
'app/core/config',
|
|
||||||
],
|
|
||||||
function (angular, _, coreModule, store, config) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
coreModule.default.service('contextSrv', function() {
|
|
||||||
|
|
||||||
function User() {
|
|
||||||
if (config.bootData.user) {
|
|
||||||
_.extend(this, config.bootData.user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasRole = function(role) {
|
|
||||||
return this.user.orgRole === role;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setPinnedState = function(val) {
|
|
||||||
this.pinned = val;
|
|
||||||
store.set('grafana.sidemenu.pinned', val);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.toggleSideMenu = function() {
|
|
||||||
this.sidemenu = !this.sidemenu;
|
|
||||||
if (!this.sidemenu) {
|
|
||||||
this.setPinnedState(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
|
|
||||||
if (this.pinned) {
|
|
||||||
this.sidemenu = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.version = config.buildInfo.version;
|
|
||||||
this.lightTheme = false;
|
|
||||||
this.user = new User();
|
|
||||||
this.isSignedIn = this.user.isSignedIn;
|
|
||||||
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
|
|
||||||
this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
73
public/app/core/services/context_srv.ts
Normal file
73
public/app/core/services/context_srv.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import coreModule from 'app/core/core_module';
|
||||||
|
import store from 'app/core/store';
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
isGrafanaAdmin: any;
|
||||||
|
isSignedIn: any;
|
||||||
|
orgRole: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (config.bootData.user) {
|
||||||
|
_.extend(this, config.bootData.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ContextSrv {
|
||||||
|
pinned: any;
|
||||||
|
version: any;
|
||||||
|
user: User;
|
||||||
|
isSignedIn: any;
|
||||||
|
isGrafanaAdmin: any;
|
||||||
|
isEditor: any;
|
||||||
|
sidemenu: any;
|
||||||
|
lightTheme: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
|
||||||
|
if (this.pinned) {
|
||||||
|
this.sidemenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.buildInfo) {
|
||||||
|
config.buildInfo = {};
|
||||||
|
}
|
||||||
|
if (!config.bootData) {
|
||||||
|
config.bootData = {user: {}, settings: {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.version = config.buildInfo.version;
|
||||||
|
this.lightTheme = false;
|
||||||
|
this.user = new User();
|
||||||
|
this.isSignedIn = this.user.isSignedIn;
|
||||||
|
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
|
||||||
|
this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRole(role) {
|
||||||
|
return this.user.orgRole === role;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPinnedState(val) {
|
||||||
|
this.pinned = val;
|
||||||
|
store.set('grafana.sidemenu.pinned', val);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSideMenu() {
|
||||||
|
this.sidemenu = !this.sidemenu;
|
||||||
|
if (!this.sidemenu) {
|
||||||
|
this.setPinnedState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextSrv = new ContextSrv();
|
||||||
|
export {contextSrv};
|
||||||
|
|
||||||
|
coreModule.factory('contextSrv', function() {
|
||||||
|
return contextSrv;
|
||||||
|
});
|
||||||
@@ -7,8 +7,5 @@ define([
|
|||||||
'./playlist/all',
|
'./playlist/all',
|
||||||
'./snapshot/all',
|
'./snapshot/all',
|
||||||
'./panel/all',
|
'./panel/all',
|
||||||
'./profile/profileCtrl',
|
|
||||||
'./profile/changePasswordCtrl',
|
|
||||||
'./profile/selectOrgCtrl',
|
|
||||||
'./styleguide/styleguide',
|
'./styleguide/styleguide',
|
||||||
], function () {});
|
], function () {});
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ function (angular, $, config, moment) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.timezoneChanged = function() {
|
||||||
|
$rootScope.$broadcast("refresh");
|
||||||
|
};
|
||||||
|
|
||||||
$scope.formatDate = function(date) {
|
$scope.formatDate = function(date) {
|
||||||
return moment(date).format('MMM Do YYYY, h:mm:ss a');
|
return moment(date).format('MMM Do YYYY, h:mm:ss a');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function (angular, $, _, moment) {
|
|||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.factory('dashboardSrv', function() {
|
module.factory('dashboardSrv', function(contextSrv) {
|
||||||
|
|
||||||
function DashboardModel (data, meta) {
|
function DashboardModel (data, meta) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -25,7 +25,7 @@ function (angular, $, _, moment) {
|
|||||||
this.originalTitle = this.title;
|
this.originalTitle = this.title;
|
||||||
this.tags = data.tags || [];
|
this.tags = data.tags || [];
|
||||||
this.style = data.style || "dark";
|
this.style = data.style || "dark";
|
||||||
this.timezone = data.timezone || 'browser';
|
this.timezone = data.timezone || '';
|
||||||
this.editable = data.editable !== false;
|
this.editable = data.editable !== false;
|
||||||
this.hideControls = data.hideControls || false;
|
this.hideControls = data.hideControls || false;
|
||||||
this.sharedCrosshair = data.sharedCrosshair || false;
|
this.sharedCrosshair = data.sharedCrosshair || false;
|
||||||
@@ -208,6 +208,14 @@ function (angular, $, _, moment) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
p.isTimezoneUtc = function() {
|
||||||
|
return this.getTimezone() === 'utc';
|
||||||
|
};
|
||||||
|
|
||||||
|
p.getTimezone = function() {
|
||||||
|
return this.timezone ? this.timezone : contextSrv.user.timezone;
|
||||||
|
};
|
||||||
|
|
||||||
p._updateSchema = function(old) {
|
p._updateSchema = function(old) {
|
||||||
var i, j, k;
|
var i, j, k;
|
||||||
var oldVersion = this.schemaVersion;
|
var oldVersion = this.schemaVersion;
|
||||||
@@ -401,11 +409,18 @@ function (angular, $, _, moment) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 11) {
|
if (oldVersion < 12) {
|
||||||
// update template variables
|
// update template variables
|
||||||
_.each(this.templating.list, function(templateVariable) {
|
_.each(this.templating.list, function(templateVariable) {
|
||||||
if (templateVariable.refresh) { templateVariable.refresh = 1; }
|
if (templateVariable.refresh) { templateVariable.refresh = 1; }
|
||||||
if (!templateVariable.refresh) { templateVariable.refresh = 0; }
|
if (!templateVariable.refresh) { templateVariable.refresh = 0; }
|
||||||
|
if (templateVariable.hideVariable) {
|
||||||
|
templateVariable.hide = 2;
|
||||||
|
} else if (templateVariable.hideLabel) {
|
||||||
|
templateVariable.hide = 1;
|
||||||
|
} else {
|
||||||
|
templateVariable.hide = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="editJson();">View JSON</a></li>
|
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="editJson();">View JSON</a></li>
|
||||||
<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
|
<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
|
||||||
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
|
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
|
||||||
|
<li ng-if="dashboard.id"><a class="pointer" ng-click="saveDashboardAsHome();">Set As Home</a></li>
|
||||||
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
|
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label width-7">Timezone</label>
|
<label class="gf-form-label width-7">Timezone</label>
|
||||||
<div class="gf-form-select-wrapper">
|
<div class="gf-form-select-wrapper">
|
||||||
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f for f in ['browser','utc']"></select>
|
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f.value as f.text for f in [{value: '', text: 'Default'}, {value: 'browser', text: 'Local browser time'},{value: 'utc', text: 'UTC'}]" ng-change="timezoneChanged()"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,66 +62,67 @@
|
|||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if="editor.index == 1">
|
</div>
|
||||||
<h5 class="section-heading">Rows settings</h5>
|
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div ng-if="editor.index == 1">
|
||||||
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
|
<h5 class="section-heading">Rows settings</h5>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
|
|
||||||
|
|
||||||
<div class="gf-form">
|
<div class="gf-form-group">
|
||||||
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
|
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
|
||||||
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
|
<div class="gf-form">
|
||||||
</button>
|
<span class="gf-form-label">Title</span>
|
||||||
<button class="btn btn-inverse gf-from-btn" ng-click="_.move(dashboard.rows,$index,$index+1)">
|
<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
|
||||||
<i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
|
</div>
|
||||||
</button>
|
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
|
||||||
<button class="btn btn-inverse gf-form-btn" click="dashboard.rows = _.without(dashboard.rows,row)">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if="editor.index == 2">
|
<div class="gf-form">
|
||||||
<dash-links-editor></dash-links-editor>
|
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
|
||||||
</div>
|
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-inverse gf-from-btn" ng-click="_.move(dashboard.rows,$index,$index+1)">
|
||||||
|
<i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-inverse gf-form-btn" click="dashboard.rows = _.without(dashboard.rows,row)">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ng-if="editor.index == 3">
|
<div ng-if="editor.index == 2">
|
||||||
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
|
<dash-links-editor></dash-links-editor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="editor.index == 4">
|
<div ng-if="editor.index == 3">
|
||||||
<h5 class="section-heading">Dashboard info</h5>
|
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
|
||||||
<div class="gf-form-group">
|
</div>
|
||||||
<div class="gf-form">
|
|
||||||
<span class="gf-form-label width-10">Last updated at:</span>
|
<div ng-if="editor.index == 4">
|
||||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
|
<h5 class="section-heading">Dashboard info</h5>
|
||||||
</div>
|
<div class="gf-form-group">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-10">Last updated by:</span>
|
<span class="gf-form-label width-10">Last updated at:</span>
|
||||||
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}} </span>
|
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-10">Created at:</span>
|
<span class="gf-form-label width-10">Last updated by:</span>
|
||||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}} </span>
|
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}} </span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-10">Created by:</span>
|
<span class="gf-form-label width-10">Created at:</span>
|
||||||
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}} </span>
|
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}} </span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-10">Current version:</span>
|
<span class="gf-form-label width-10">Created by:</span>
|
||||||
<span class="gf-form-label width-18">{{dashboardMeta.version}} </span>
|
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}} </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="gf-form">
|
||||||
</div>
|
<span class="gf-form-label width-10">Current version:</span>
|
||||||
|
<span class="gf-form-label width-18">{{dashboardMeta.version}} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="submenu-controls">
|
<div class="submenu-controls">
|
||||||
<ul ng-if="ctrl.dashboard.templating.list.length > 0">
|
<ul ng-if="ctrl.dashboard.templating.list.length > 0">
|
||||||
<li ng-repeat="variable in ctrl.variables" ng-show="!variable.hideVariable" class="submenu-item">
|
<li ng-repeat="variable in ctrl.variables" ng-hide="variable.hide === 2" class="submenu-item">
|
||||||
<span class="submenu-item-label template-variable " ng-show="!variable.hideLabel">
|
<span class="submenu-item-label template-variable " ng-hide="variable.hide === 1">
|
||||||
{{variable.label || variable.name}}:
|
{{variable.label || variable.name}}:
|
||||||
</span>
|
</span>
|
||||||
<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
|
<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class TimePickerCtrl {
|
|||||||
var time = angular.copy(this.timeSrv.timeRange());
|
var time = angular.copy(this.timeSrv.timeRange());
|
||||||
var timeRaw = angular.copy(this.timeSrv.timeRange(false));
|
var timeRaw = angular.copy(this.timeSrv.timeRange(false));
|
||||||
|
|
||||||
if (this.dashboard.timezone === 'browser') {
|
if (!this.dashboard.isTimezoneUtc()) {
|
||||||
time.from.local();
|
time.from.local();
|
||||||
time.to.local();
|
time.to.local();
|
||||||
if (moment.isMoment(timeRaw.from)) {
|
if (moment.isMoment(timeRaw.from)) {
|
||||||
@@ -125,7 +125,7 @@ export class TimePickerCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAbsoluteMomentForTimezone(jsDate) {
|
getAbsoluteMomentForTimezone(jsDate) {
|
||||||
return this.dashboard.timezone === 'browser' ? moment(jsDate) : moment(jsDate).utc();
|
return this.dashboard.isTimezoneUtc() ? moment(jsDate).utc() : moment(jsDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
setRelativeFilter(timespan) {
|
setRelativeFilter(timespan) {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
define([
|
define([
|
||||||
'./org_users_ctrl',
|
'./org_users_ctrl',
|
||||||
|
'./profile_ctrl',
|
||||||
|
'./org_users_ctrl',
|
||||||
|
'./select_org_ctrl',
|
||||||
|
'./change_password_ctrl',
|
||||||
'./newOrgCtrl',
|
'./newOrgCtrl',
|
||||||
'./userInviteCtrl',
|
'./userInviteCtrl',
|
||||||
'./orgApiKeysCtrl',
|
'./orgApiKeysCtrl',
|
||||||
'./orgDetailsCtrl',
|
'./orgDetailsCtrl',
|
||||||
|
'./prefs_control',
|
||||||
], function () {});
|
], function () {});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<prefs-control mode="org"></prefs-control>
|
||||||
|
|
||||||
<h3 class="page-heading">Address</h3>
|
<h3 class="page-heading">Address</h3>
|
||||||
|
|
||||||
|
|||||||
@@ -6,31 +6,28 @@
|
|||||||
<h1>Profile</h1>
|
<h1>Profile</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form name="userForm" class="gf-form-group">
|
<form name="ctrl.userForm" class="gf-form-group">
|
||||||
<h3 class="page-heading">Preferences</h3>
|
<h3 class="page-heading">Information</h3>
|
||||||
|
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-7">Name</span>
|
<span class="gf-form-label width-9">Name</span>
|
||||||
<input class="gf-form-input max-width-21" type="text" required ng-model="user.name" >
|
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.name" >
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-7">Email</span>
|
<span class="gf-form-label width-9">Email</span>
|
||||||
<input class="gf-form-input max-width-21" type="email" required ng-model="user.email">
|
<input class="gf-form-input max-width-21" type="email" required ng-model="ctrl.user.email">
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-7">Username</span>
|
<span class="gf-form-label width-9">Username</span>
|
||||||
<input class="gf-form-input max-width-21" type="text" required ng-model="user.login">
|
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.login">
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
|
||||||
<span class="gf-form-label width-7">UI Theme</span>
|
|
||||||
<select class="gf-form-input gf-size-auto" ng-model="user.theme" ng-options="f for f in ['dark', 'light']"></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
<div class="gf-form-button-row">
|
||||||
<button type="submit" class="btn btn-success" ng-click="update()">Update</button>
|
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Update</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<prefs-control mode="user"></prefs-control>
|
||||||
|
|
||||||
<h3 class="page-heading">Password</h3>
|
<h3 class="page-heading">Password</h3>
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<a href="profile/password" class="btn btn-inverse">Change Password</a>
|
<a href="profile/password" class="btn btn-inverse">Change Password</a>
|
||||||
@@ -47,7 +44,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="org in orgs">
|
<tr ng-repeat="org in ctrl.orgs">
|
||||||
<td>{{org.name}}</td>
|
<td>{{org.name}}</td>
|
||||||
<td>{{org.role}}</td>
|
<td>{{org.role}}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
98
public/app/features/org/prefs_control.ts
Normal file
98
public/app/features/org/prefs_control.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import coreModule from 'app/core/core_module';
|
||||||
|
|
||||||
|
export class PrefsControlCtrl {
|
||||||
|
prefs: any;
|
||||||
|
oldTheme: any;
|
||||||
|
prefsForm: any;
|
||||||
|
mode: string;
|
||||||
|
|
||||||
|
timezones: any = [
|
||||||
|
{value: '', text: 'Default'},
|
||||||
|
{value: 'browser', text: 'Local browser time'},
|
||||||
|
{value: 'utc', text: 'UTC'},
|
||||||
|
];
|
||||||
|
themes: any = [
|
||||||
|
{value: '', text: 'Default'},
|
||||||
|
{value: 'dark', text: 'Dark'},
|
||||||
|
{value: 'light', text: 'Light'},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @ngInject **/
|
||||||
|
constructor(private backendSrv, private $location) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
return this.backendSrv.get(`/api/${this.mode}/preferences`).then(prefs => {
|
||||||
|
this.prefs = prefs;
|
||||||
|
this.oldTheme = prefs.theme;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePrefs() {
|
||||||
|
if (!this.prefsForm.$valid) { return; }
|
||||||
|
|
||||||
|
var cmd = {
|
||||||
|
theme: this.prefs.theme,
|
||||||
|
timezone: this.prefs.timezone,
|
||||||
|
homeDashboardId: this.prefs.homeDashboardId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.backendSrv.put(`/api/${this.mode}/preferences`, cmd).then(() => {
|
||||||
|
window.location.href = config.appSubUrl + this.$location.path();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = `
|
||||||
|
<form name="ctrl.prefsForm" class="gf-form-group">
|
||||||
|
<h3 class="page-heading">Preferences</h3>
|
||||||
|
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-9">UI Theme</span>
|
||||||
|
<div class="gf-form-select-wrapper max-width-20">
|
||||||
|
<select class="gf-form-input" ng-model="ctrl.prefs.theme" ng-options="f.value as f.text for f in ctrl.themes"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-9">Home Dashboard</span>
|
||||||
|
<dashboard-selector
|
||||||
|
class="gf-form-select-wrapper max-width-20"
|
||||||
|
model="ctrl.prefs.homeDashboardId">
|
||||||
|
</dashboard-selector>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label width-9">Timezone</label>
|
||||||
|
<div class="gf-form-select-wrapper max-width-20">
|
||||||
|
<select class="gf-form-input" ng-model="ctrl.prefs.timezone" ng-options="f.value as f.text for f in ctrl.timezones"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-button-row">
|
||||||
|
<button type="submit" class="btn btn-success" ng-click="ctrl.updatePrefs()">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function prefsControlDirective() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
controller: PrefsControlCtrl,
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: 'ctrl',
|
||||||
|
template: template,
|
||||||
|
scope: {
|
||||||
|
mode: "@"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.directive('prefsControl', prefsControlDirective);
|
||||||
|
|
||||||
|
|
||||||
51
public/app/features/org/profile_ctrl.ts
Normal file
51
public/app/features/org/profile_ctrl.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import {coreModule} from 'app/core/core';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export class ProfileCtrl {
|
||||||
|
user: any;
|
||||||
|
old_theme: any;
|
||||||
|
orgs: any;
|
||||||
|
userForm: any;
|
||||||
|
|
||||||
|
/** @ngInject **/
|
||||||
|
constructor(private backendSrv, private contextSrv, private $location) {
|
||||||
|
this.getUser();
|
||||||
|
this.getUserOrgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser() {
|
||||||
|
this.backendSrv.get('/api/user').then(user => {
|
||||||
|
this.user = user;
|
||||||
|
this.user.theme = user.theme || 'dark';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserOrgs() {
|
||||||
|
this.backendSrv.get('/api/user/orgs').then(orgs => {
|
||||||
|
this.orgs = orgs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setUsingOrg(org) {
|
||||||
|
this.backendSrv.post('/api/user/using/' + org.orgId).then(() => {
|
||||||
|
window.location.href = config.appSubUrl + '/profile';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if (!this.userForm.$valid) { return; }
|
||||||
|
|
||||||
|
this.backendSrv.put('/api/user/', this.user).then(() => {
|
||||||
|
this.contextSrv.user.name = this.user.name || this.user.login;
|
||||||
|
if (this.old_theme !== this.user.theme) {
|
||||||
|
window.location.href = config.appSubUrl + this.$location.path();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.controller('ProfileCtrl', ProfileCtrl);
|
||||||
@@ -165,6 +165,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
|
|
||||||
issueQueries(datasource) {
|
issueQueries(datasource) {
|
||||||
this.updateTimeRange();
|
this.updateTimeRange();
|
||||||
|
this.datasource = datasource;
|
||||||
|
|
||||||
if (!this.panel.targets || this.panel.targets.length === 0) {
|
if (!this.panel.targets || this.panel.targets.length === 0) {
|
||||||
return this.$q.when([]);
|
return this.$q.when([]);
|
||||||
@@ -251,7 +252,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
|
|
||||||
addDataQuery(datasource) {
|
addDataQuery(datasource) {
|
||||||
var target = {
|
var target = {
|
||||||
datasource: datasource ? datasource.name : undefined
|
|
||||||
};
|
};
|
||||||
this.panel.targets.push(target);
|
this.panel.targets.push(target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class PanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.$on("refresh", () => this.refresh());
|
$scope.$on("refresh", () => this.refresh());
|
||||||
$scope.$on("render", () => this.calculatePanelHeight());
|
$scope.$on("render", () => this.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -149,6 +149,7 @@ export class PanelCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.calculatePanelHeight();
|
||||||
this.events.emit('render', payload);
|
this.events.emit('render', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export class DashImportListCtrl {
|
|||||||
plugin: any;
|
plugin: any;
|
||||||
datasource: any;
|
datasource: any;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
constructor($scope, private $http, private backendSrv, private $rootScope) {
|
constructor($scope, private $http, private backendSrv, private $rootScope) {
|
||||||
this.dashboards = [];
|
this.dashboards = [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
define([
|
|
||||||
'angular',
|
|
||||||
'app/core/config',
|
|
||||||
],
|
|
||||||
function (angular, config) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var module = angular.module('grafana.controllers');
|
|
||||||
|
|
||||||
module.controller('ProfileCtrl', function($scope, backendSrv, contextSrv, $location) {
|
|
||||||
|
|
||||||
$scope.init = function() {
|
|
||||||
$scope.getUser();
|
|
||||||
$scope.getUserOrgs();
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.getUser = function() {
|
|
||||||
backendSrv.get('/api/user').then(function(user) {
|
|
||||||
$scope.user = user;
|
|
||||||
$scope.user.theme = user.theme || 'dark';
|
|
||||||
$scope.old_theme = $scope.user.theme;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.getUserOrgs = function() {
|
|
||||||
backendSrv.get('/api/user/orgs').then(function(orgs) {
|
|
||||||
$scope.orgs = orgs;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.setUsingOrg = function(org) {
|
|
||||||
backendSrv.post('/api/user/using/' + org.orgId).then(function() {
|
|
||||||
window.location.href = config.appSubUrl + '/profile';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.update = function() {
|
|
||||||
if (!$scope.userForm.$valid) { return; }
|
|
||||||
|
|
||||||
backendSrv.put('/api/user/', $scope.user).then(function() {
|
|
||||||
contextSrv.user.name = $scope.user.name || $scope.user.login;
|
|
||||||
if ($scope.old_theme !== $scope.user.theme) {
|
|
||||||
window.location.href = config.appSubUrl + $location.path();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.init();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -14,6 +14,7 @@ function (angular, _) {
|
|||||||
datasource: null,
|
datasource: null,
|
||||||
refresh: 0,
|
refresh: 0,
|
||||||
name: '',
|
name: '',
|
||||||
|
hide: 0,
|
||||||
options: [],
|
options: [],
|
||||||
includeAll: false,
|
includeAll: false,
|
||||||
multi: false,
|
multi: false,
|
||||||
@@ -25,6 +26,12 @@ function (angular, _) {
|
|||||||
{value: 2, text: "On Time Range Change"},
|
{value: 2, text: "On Time Range Change"},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$scope.hideOptions = [
|
||||||
|
{value: 0, text: ""},
|
||||||
|
{value: 1, text: "Label"},
|
||||||
|
{value: 2, text: "Variable"},
|
||||||
|
];
|
||||||
|
|
||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
$scope.mode = 'list';
|
$scope.mode = 'list';
|
||||||
|
|
||||||
|
|||||||
@@ -74,32 +74,33 @@
|
|||||||
<h5 class="section-heading">Variable</h5>
|
<h5 class="section-heading">Variable</h5>
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form max-width-19">
|
||||||
<span class="gf-form-label width-7">Name</span>
|
<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>
|
<input type="text" class="gf-form-input max-width-12" placeholder="name" ng-model='current.name'></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-4">Type</span>
|
<span class="gf-form-label width-4">Type</span>
|
||||||
<div class="gf-form-select-wrapper max-width-10">
|
<div class="gf-form-select-wrapper">
|
||||||
<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>
|
<select class="gf-form-input width-7" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form max-width-19">
|
||||||
<span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span>
|
<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'">
|
<div class="gf-form-select-wrapper max-width-12" 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>
|
<select class="gf-form-input" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form max-width-19">
|
||||||
<span class="gf-form-label width-7">Label</span>
|
<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>
|
<input type="text" class="gf-form-input max-width-12" ng-model='current.label' placeholder="optional display name"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-4">Hide</span>
|
<span class="gf-form-label width-4">Hide</span>
|
||||||
<editor-checkbox text="Label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
|
<div class="gf-form-select-wrapper">
|
||||||
<editor-checkbox text="Variable" model="current.hideVariable" change="runQuery()"></editor-checkbox>
|
<select class="gf-form-input width-7" ng-model="current.hide" ng-options="f.value as f.text for f in hideOptions"></select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -176,17 +177,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-group" >
|
<div class="section gf-form-group" >
|
||||||
<h5 class="section-heading">Selection Options</h5>
|
<h5 class="section-heading">Selection Options</h5>
|
||||||
<div class="gf-form">
|
<gf-form-switch class="gf-form"
|
||||||
<span class="gf-form-label width-10">Multi-value</span>
|
label="Multi-value"
|
||||||
<editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
|
label-class="width-10"
|
||||||
<tip>Enables multiple values to be selected at the same time</tip>
|
tooltip="Enables multiple values to be selected at the same time"
|
||||||
</div>
|
checked="current.multi"
|
||||||
<div class="gf-form">
|
on-change="runQuery()">
|
||||||
<span class="gf-form-label width-10">Include All option</span>
|
</gf-form-switch>
|
||||||
<editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
|
<gf-form-switch class="gf-form"
|
||||||
</div>
|
label="Include All option"
|
||||||
|
label-class="width-10"
|
||||||
|
checked="current.includeAll"
|
||||||
|
on-change="runQuery()">
|
||||||
|
</gf-form-switch>
|
||||||
|
|
||||||
<div class="gf-form" ng-if="current.includeAll">
|
<div class="gf-form" ng-if="current.includeAll">
|
||||||
<span class="gf-form-label width-10">Custom all value</span>
|
<span class="gf-form-label width-10">Custom all value</span>
|
||||||
<input type="text" class="gf-form-input max-width-15" ng-model='current.allValue' placeholder="blank = auto"></input>
|
<input type="text" class="gf-form-input max-width-15" ng-model='current.allValue' placeholder="blank = auto"></input>
|
||||||
|
|||||||
7
public/app/plugins/datasource/cloudwatch/README.md
Normal file
7
public/app/plugins/datasource/cloudwatch/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# CloudWatch Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **built in** support for CloudWatch. You just have to add it as a data source and you will be ready to build dashboards for you CloudWatch metrics.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/cloudwatch/](http://docs.grafana.org/datasources/cloudwatch/)
|
||||||
7
public/app/plugins/datasource/elasticsearch/README.md
Normal file
7
public/app/plugins/datasource/elasticsearch/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# CloudWatch Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **advanced support** for Elasticsearch. You can do many types of simple or complex elasticsearch queries to visualize logs or metrics stored in Elasticsearch. You can also annotate your graphs with log events stored in Elasticsearch.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/elasticsearch/](http://docs.grafana.org/datasources/elasticsearch/)
|
||||||
@@ -77,7 +77,7 @@ function (angular, _, queryDef) {
|
|||||||
switch($scope.agg.type) {
|
switch($scope.agg.type) {
|
||||||
case 'terms': {
|
case 'terms': {
|
||||||
settings.order = settings.order || "asc";
|
settings.order = settings.order || "asc";
|
||||||
settings.size = settings.size || "0";
|
settings.size = settings.size || "10";
|
||||||
settings.orderBy = settings.orderBy || "_term";
|
settings.orderBy = settings.orderBy || "_term";
|
||||||
|
|
||||||
if (settings.size !== '0') {
|
if (settings.size !== '0') {
|
||||||
|
|||||||
3
public/app/plugins/datasource/grafana/README.md
Normal file
3
public/app/plugins/datasource/grafana/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Grafana Fake Data Datasource - Native Plugin
|
||||||
|
|
||||||
|
This is the built in Fake Data Datasource that is used before any datasources are set up in your Grafana installation. It means you can create a graph without any data and still get an idea of what it would look like.
|
||||||
9
public/app/plugins/datasource/graphite/README.md
Normal file
9
public/app/plugins/datasource/graphite/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Graphite Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **built in** support for Graphite (of course!).
|
||||||
|
|
||||||
|
Grafana has an advanced Graphite query editor that lets you quickly navigate the metric space, add functions, change function parameters and much more. The editor can handle all types of graphite queries. It can even handle complex nested queries through the use of query references.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/graphite/](http://docs.grafana.org/datasources/graphite/)
|
||||||
13
public/app/plugins/datasource/influxdb/README.md
Normal file
13
public/app/plugins/datasource/influxdb/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# InfluxDB Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **built in** support for InfluxDB 0.9.
|
||||||
|
|
||||||
|
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x. The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles them as different data sources.
|
||||||
|
|
||||||
|
This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API.
|
||||||
|
|
||||||
|
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://www.grafana.net/plugins/grafana-influxdb-08-datasource).
|
||||||
|
|
||||||
|
Read more about InfluxDB here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
|
||||||
@@ -193,7 +193,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
|||||||
|
|
||||||
if (addTemplateVars) {
|
if (addTemplateVars) {
|
||||||
for (let variable of this.templateSrv.variables) {
|
for (let variable of this.templateSrv.variables) {
|
||||||
segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true }));
|
segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/^$' + variable.name + '$/', expandable: true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default class ResponseParser {
|
|||||||
var series = influxResults.series[0];
|
var series = influxResults.series[0];
|
||||||
return _.map(series.values, (value) => {
|
return _.map(series.values, (value) => {
|
||||||
if (_.isArray(value)) {
|
if (_.isArray(value)) {
|
||||||
if (query.indexOf('SHOW TAG VALUES') >= 0) {
|
if (query.toLowerCase().indexOf('show tag values') >= 0) {
|
||||||
return { text: (value[1] || value[0]) };
|
return { text: (value[1] || value[0]) };
|
||||||
} else {
|
} else {
|
||||||
return { text: value[0] };
|
return { text: value[0] };
|
||||||
|
|||||||
3
public/app/plugins/datasource/mixed/README.md
Normal file
3
public/app/plugins/datasource/mixed/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Mixed Datasource - Native Plugin
|
||||||
|
|
||||||
|
This is a **built in** datasource that allows you to mix different datasource on the same graph! You can enable this by selecting the built in -- Mixed -- data source. When selected this will allow you to specify data source on a per query basis. This will, for example, allow you to plot metrics from different Graphite servers on the same Graph or plot data from Elasticsearch alongside data from Prometheus. Mixing different data sources on the same graph works for any data source, even custom ones.
|
||||||
7
public/app/plugins/datasource/opentsdb/README.md
Normal file
7
public/app/plugins/datasource/opentsdb/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# OpenTSDB Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **built in** support for OpenTSDB, a scalable, distributed time series database.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/opentsdb/](http://docs.grafana.org/datasources/opentsdb/)
|
||||||
7
public/app/plugins/datasource/prometheus/README.md
Normal file
7
public/app/plugins/datasource/prometheus/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Prometheus Datasource - Native Plugin
|
||||||
|
|
||||||
|
Grafana ships with **built in** support for Prometheus, the open-source service monitoring system and time series database.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/datasources/prometheus/](http://docs.grafana.org/datasources/prometheus/)
|
||||||
9
public/app/plugins/panel/dashlist/README.md
Normal file
9
public/app/plugins/panel/dashlist/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Dashlist Panel - Native Plugin
|
||||||
|
|
||||||
|
This Dashlist panel is **included** with Grafana.
|
||||||
|
|
||||||
|
The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, a search query and/or dashboard tags.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/reference/dashlist/](http://docs.grafana.org/reference/dashlist/)
|
||||||
@@ -25,7 +25,7 @@ class DashListCtrl extends PanelCtrl {
|
|||||||
_.defaults(this.panel, panelDefaults);
|
_.defaults(this.panel, panelDefaults);
|
||||||
|
|
||||||
if (this.panel.tag) {
|
if (this.panel.tag) {
|
||||||
this.panel.tags = [$scope.panel.tag];
|
this.panel.tags = [this.panel.tag];
|
||||||
delete this.panel.tag;
|
delete this.panel.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
public/app/plugins/panel/graph/README.md
Normal file
7
public/app/plugins/panel/graph/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Graph Panel - Native Plugin
|
||||||
|
|
||||||
|
The Graph is the main graph panel and is **included** with Grafana. It provides a very rich set of graphing options.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/reference/graph/](http://docs.grafana.org/reference/graph/)
|
||||||
@@ -279,7 +279,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
|||||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
|
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
|
||||||
|
|
||||||
options.xaxis = {
|
options.xaxis = {
|
||||||
timezone: dashboard.timezone,
|
timezone: dashboard.getTimezone(),
|
||||||
show: panel['x-axis'],
|
show: panel['x-axis'],
|
||||||
mode: "time",
|
mode: "time",
|
||||||
min: min,
|
min: min,
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ function (angular, _, $) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var html = '<div class="graph-legend-series';
|
var html = '<div class="graph-legend-series';
|
||||||
if (series.yaxis === 2) { html += ' pull-right'; }
|
if (series.yaxis === 2) { html += ' graph-legend-series--right-y'; }
|
||||||
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
|
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
|
||||||
html += '" data-series-index="' + i + '">';
|
html += '" data-series-index="' + i + '">';
|
||||||
html += '<div class="graph-legend-icon">';
|
html += '<div class="graph-legend-icon">';
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ describe('grafanaGraph', function() {
|
|||||||
},
|
},
|
||||||
renderingCompleted: sinon.spy(),
|
renderingCompleted: sinon.spy(),
|
||||||
hiddenSeries: {},
|
hiddenSeries: {},
|
||||||
dashboard: {timezone: 'browser'},
|
dashboard: {
|
||||||
|
getTimezone: sinon.stub().returns('browser')
|
||||||
|
},
|
||||||
range: {
|
range: {
|
||||||
from: moment([2015, 1, 1, 10]),
|
from: moment([2015, 1, 1, 10]),
|
||||||
to: moment([2015, 1, 1, 22]),
|
to: moment([2015, 1, 1, 22]),
|
||||||
|
|||||||
@@ -41,30 +41,32 @@
|
|||||||
<h5 class="section-heading">X-Axis</h5>
|
<h5 class="section-heading">X-Axis</h5>
|
||||||
<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
|
<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section gf-form-group">
|
<div class="section gf-form-group">
|
||||||
<h5 class="section-heading">Thresholds</h5>
|
<h5 class="section-heading">Thresholds</h5>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label width-5">Level 1</label>
|
<label class="gf-form-label width-5">Level 1</label>
|
||||||
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold1" ng-change="ctrl.render()" ng-model-onblur>
|
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold1" ng-change="ctrl.render()" ng-model-onblur>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label width-5">Color</label>
|
<label class="gf-form-label width-5">Color</label>
|
||||||
<div class="gf-form-label">
|
<div class="gf-form-label">
|
||||||
<spectrum-picker ng-model="ctrl.panel.grid.threshold1Color" ng-change="ctrl.render()" ></spectrum-picker>
|
<spectrum-picker ng-model="ctrl.panel.grid.threshold1Color" ng-change="ctrl.render()" ></spectrum-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
</div>
|
||||||
<label class="gf-form-label width-5">Level 2</label>
|
<div class="gf-form-inline">
|
||||||
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold2" ng-change="ctrl.render()" ng-model-onblur>
|
<div class="gf-form">
|
||||||
</div>
|
<label class="gf-form-label width-5">Level 2</label>
|
||||||
<div class="gf-form">
|
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold2" ng-change="ctrl.render()" ng-model-onblur>
|
||||||
<label class="gf-form-label width-5">Color</label>
|
</div>
|
||||||
<div class="gf-form-label">
|
<div class="gf-form">
|
||||||
<spectrum-picker ng-model="ctrl.panel.grid.threshold2Color" ng-change="ctrl.render()" ></spectrum-picker>
|
<label class="gf-form-label width-5">Color</label>
|
||||||
</div>
|
<div class="gf-form-label">
|
||||||
</div>
|
<spectrum-picker ng-model="ctrl.panel.grid.threshold2Color" ng-change="ctrl.render()" ></spectrum-picker>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
9
public/app/plugins/panel/singlestat/README.md
Normal file
9
public/app/plugins/panel/singlestat/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Singlestat Panel - Native Plugin
|
||||||
|
|
||||||
|
The Singlestat Panel is **included** with Grafana.
|
||||||
|
|
||||||
|
The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/reference/singlestat/](http://docs.grafana.org/reference/singlestat/)
|
||||||
9
public/app/plugins/panel/table/README.md
Normal file
9
public/app/plugins/panel/table/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Table Panel - Native Plugin
|
||||||
|
|
||||||
|
The Table Panel is **included** with Grafana.
|
||||||
|
|
||||||
|
The table panel is very flexible, supporting both multiple modes for time series as well as for table, annotation and raw JSON data. It also provides date formatting and value formatting and coloring options.
|
||||||
|
|
||||||
|
Check out the [Table Panel Showcase in the Grafana Playground](http://play.grafana.org/dashboard/db/table-panel-showcase) or read more about it here:
|
||||||
|
|
||||||
|
[http://docs.grafana.org/reference/table_panel/](http://docs.grafana.org/reference/table_panel/)
|
||||||
@@ -114,9 +114,13 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
this.table = transformDataToTable(this.dataRaw, this.panel);
|
this.table = transformDataToTable(this.dataRaw, this.panel);
|
||||||
this.table.sort(this.panel.sort);
|
this.table.sort(this.panel.sort);
|
||||||
this.render(this.table);
|
return super.render(this.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleColumnSort(col, colIndex) {
|
toggleColumnSort(col, colIndex) {
|
||||||
@@ -130,7 +134,6 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
this.panel.sort.col = colIndex;
|
this.panel.sort.col = colIndex;
|
||||||
this.panel.sort.desc = true;
|
this.panel.sort.desc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendTableRows(tbodyElem) {
|
function appendTableRows(tbodyElem) {
|
||||||
var renderer = new TableRenderer(panel, data, ctrl.dashboard.timezone);
|
var renderer = new TableRenderer(panel, data, ctrl.dashboard.isTimezoneUtc());
|
||||||
tbodyElem.empty();
|
tbodyElem.empty();
|
||||||
tbodyElem.html(renderer.render(ctrl.pageIndex));
|
tbodyElem.html(renderer.render(ctrl.pageIndex));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class TableRenderer {
|
|||||||
formaters: any[];
|
formaters: any[];
|
||||||
colorState: any;
|
colorState: any;
|
||||||
|
|
||||||
constructor(private panel, private table, private timezone) {
|
constructor(private panel, private table, private isUtc) {
|
||||||
this.formaters = [];
|
this.formaters = [];
|
||||||
this.colorState = {};
|
this.colorState = {};
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export class TableRenderer {
|
|||||||
return v => {
|
return v => {
|
||||||
if (_.isArray(v)) { v = v[0]; }
|
if (_.isArray(v)) { v = v[0]; }
|
||||||
var date = moment(v);
|
var date = moment(v);
|
||||||
if (this.timezone === 'utc') {
|
if (this.isUtc) {
|
||||||
date = date.utc();
|
date = date.utc();
|
||||||
}
|
}
|
||||||
return date.format(style.dateFormat);
|
return date.format(style.dateFormat);
|
||||||
|
|||||||
5
public/app/plugins/panel/text/README.md
Normal file
5
public/app/plugins/panel/text/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Text Panel - Native Plugin
|
||||||
|
|
||||||
|
The Text Panel is **included** with Grafana.
|
||||||
|
|
||||||
|
The Text Panel is a very simple panel that displays text. The source text is written in the Markdown syntax meaning you can format the text. Read [GitHub's Mastering Markdown](https://guides.github.com/features/mastering-markdown/) to learn more.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
@import "base/grid";
|
@import "base/grid";
|
||||||
@import "base/font_awesome";
|
@import "base/font_awesome";
|
||||||
@import "base/grafana_icons";
|
@import "base/grafana_icons";
|
||||||
|
@import "base/code";
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
@import "utils/utils";
|
@import "utils/utils";
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
// ---------------------
|
// ---------------------
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
div.panel {
|
div.panel {
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
padding: 0px;
|
padding: 0px !important;
|
||||||
}
|
}
|
||||||
.panel-margin {
|
.panel-margin {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|||||||
@@ -11,28 +11,28 @@ pre {
|
|||||||
background-color: $code-tag-bg;
|
background-color: $code-tag-bg;
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
border: 1px solid darken($code-tag-bg, 15%);
|
border: 1px solid darken($code-tag-bg, 15%);
|
||||||
padding: 2px;
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline code
|
// Inline code
|
||||||
code {
|
code {
|
||||||
color: #d14;
|
color: $text-color;
|
||||||
background-color: #f7f7f9;
|
background-color: $code-tag-bg;
|
||||||
border: 1px solid #e1e1e8;
|
border: 1px solid darken($code-tag-bg, 15%);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks of code
|
// Blocks of code
|
||||||
pre {
|
pre {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 0 $line-height-base / 2;
|
margin: 0 0 $line-height-base;
|
||||||
font-size: $font-size-base - 1; // 14px to 13px
|
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
background-color: #f5f5f5;
|
background-color: $code-tag-bg;
|
||||||
|
|
||||||
// Make prettyprint styles more spaced out for readability
|
// Make prettyprint styles more spaced out for readability
|
||||||
&.prettyprint {
|
&.prettyprint {
|
||||||
|
|||||||
@@ -66,12 +66,6 @@
|
|||||||
border: solid 0.08em #eeeeee;
|
border: solid 0.08em #eeeeee;
|
||||||
border-radius: .1em;
|
border-radius: .1em;
|
||||||
}
|
}
|
||||||
.pull-right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.pull-left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.fa.pull-left {
|
.fa.pull-left {
|
||||||
margin-right: .3em;
|
margin-right: .3em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,10 @@
|
|||||||
float: left;
|
float: left;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|
||||||
|
&--right-y {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-legend-value {
|
.graph-legend-value {
|
||||||
@@ -83,14 +87,22 @@
|
|||||||
.graph-legend-table {
|
.graph-legend-table {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
.graph-legend-series { display: table-row;
|
.graph-legend-series {
|
||||||
|
display: table-row;
|
||||||
float: none;
|
float: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
&.pull-right {
|
&--right-y {
|
||||||
float: none;
|
float: none;
|
||||||
|
|
||||||
|
.graph-legend-alias:after {
|
||||||
|
content: '(right-y)';
|
||||||
|
padding: 0 5px;
|
||||||
|
color: $text-color-weak;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
td, .graph-legend-alias, .graph-legend-icon, .graph-legend-value {
|
td, .graph-legend-alias, .graph-legend-icon, .graph-legend-value {
|
||||||
float: none;
|
float: none;
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ define([
|
|||||||
var _dashboardSrv;
|
var _dashboardSrv;
|
||||||
|
|
||||||
beforeEach(module('grafana.services'));
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('contextSrv', {
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(inject(function(dashboardSrv) {
|
beforeEach(inject(function(dashboardSrv) {
|
||||||
_dashboardSrv = dashboardSrv;
|
_dashboardSrv = dashboardSrv;
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ define([
|
|||||||
ctx.setup = function (setupFunc) {
|
ctx.setup = function (setupFunc) {
|
||||||
|
|
||||||
beforeEach(module('grafana.services'));
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('contextSrv', {
|
||||||
|
user: { timezone: 'utc'}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) {
|
beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) {
|
||||||
ctx.dynamicDashboardSrv = dynamicDashboardSrv;
|
ctx.dynamicDashboardSrv = dynamicDashboardSrv;
|
||||||
@@ -45,10 +50,10 @@ define([
|
|||||||
value: ['se1', 'se2', 'se3']
|
value: ['se1', 'se2', 'se3']
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{text: 'se1', value: 'se1', selected: true},
|
{text: 'se1', value: 'se1', selected: true},
|
||||||
{text: 'se2', value: 'se2', selected: true},
|
{text: 'se2', value: 'se2', selected: true},
|
||||||
{text: 'se3', value: 'se3', selected: true},
|
{text: 'se3', value: 'se3', selected: true},
|
||||||
{text: 'se4', value: 'se4', selected: false}
|
{text: 'se4', value: 'se4', selected: false}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
2
public/vendor/angular/.bower.json
vendored
2
public/vendor/angular/.bower.json
vendored
@@ -13,6 +13,6 @@
|
|||||||
"commit": "5a07c5107b4d24f41744a02b07717d55bad88e70"
|
"commit": "5a07c5107b4d24f41744a02b07717d55bad88e70"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/angular/bower-angular.git",
|
"_source": "git://github.com/angular/bower-angular.git",
|
||||||
"_target": "1.5.3",
|
"_target": "~1.5.3",
|
||||||
"_originalSource": "angular"
|
"_originalSource": "angular"
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,6 @@ module.exports = function(config) {
|
|||||||
|
|
||||||
js: {
|
js: {
|
||||||
src: [
|
src: [
|
||||||
'<%= genDir %>/vendor/npm/es5-shim/es5-shim.js',
|
|
||||||
'<%= genDir %>/vendor/npm/es6-shim/es6-shim.js',
|
'<%= genDir %>/vendor/npm/es6-shim/es6-shim.js',
|
||||||
'<%= genDir %>/vendor/npm/es6-promise/dist/es6-promise.js',
|
'<%= genDir %>/vendor/npm/es6-promise/dist/es6-promise.js',
|
||||||
'<%= genDir %>/vendor/npm/systemjs/dist/system-polyfills.js',
|
'<%= genDir %>/vendor/npm/systemjs/dist/system-polyfills.js',
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ module.exports = function(config) {
|
|||||||
'angular2/manual_typings/**/*',
|
'angular2/manual_typings/**/*',
|
||||||
'systemjs/dist/*.js',
|
'systemjs/dist/*.js',
|
||||||
'es6-promise/**/*',
|
'es6-promise/**/*',
|
||||||
'es5-shim/*.js',
|
|
||||||
'es6-shim/*.js',
|
'es6-shim/*.js',
|
||||||
'reflect-metadata/*.js',
|
'reflect-metadata/*.js',
|
||||||
'reflect-metadata/*.ts',
|
'reflect-metadata/*.ts',
|
||||||
|
|||||||
Reference in New Issue
Block a user