Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4832ecf3e7 | |||
| 2e8ed97355 | |||
| d98839fc19 | |||
| 75555c470b | |||
| b3f3b70b90 | |||
| 924800b7df | |||
| d27da57b02 | |||
| f1a01aed7e | |||
| c1a5771fcd | |||
| 8ac5c36735 | |||
| 6355896584 | |||
| 6216ce455f | |||
| c0832995ae | |||
| 9469b20cbd | |||
| 573457c03f | |||
| 5c395424c6 | |||
| f6d53df282 | |||
| e4374b1d56 | |||
| 7302ddaba5 | |||
| 7456514816 | |||
| d9ad4cf2fc | |||
| 8b4efbed06 | |||
| 10131aa8a0 | |||
| 23dc85c1de | |||
| 338c6b5a5c | |||
| c13ebce2f6 | |||
| b6ccd7ffbe | |||
| ad0794cc59 | |||
| 6961cb7440 | |||
| 9868edb138 | |||
| e822fad505 | |||
| 3ab5427019 | |||
| 7afb6fa3e3 | |||
| 29dff7ba1a | |||
| 329ee8d9f1 | |||
| f70e0003f8 | |||
| bb0eb29c82 | |||
| 47bba71d7f | |||
| 6b8921c8c5 | |||
| 7d09579e3f | |||
| e5c9a24c33 | |||
| e7626befc5 | |||
| 905d31442c | |||
| 917fabbc9b | |||
| 963001ba1a | |||
| 782c484cad | |||
| 859ef48e51 | |||
| 51aed2dbc6 | |||
| 497e9343aa | |||
| 6ce934f1dd | |||
| 3a64a11745 | |||
| f4fc3f48a7 | |||
| 9e15320fd0 | |||
| 94729745dc | |||
| 7031645f4b | |||
| f1ff044967 | |||
| 9c14b9672f | |||
| f93b039e42 | |||
| 3849b59627 | |||
| a5eda6a87b | |||
| 31441f0b43 | |||
| 451a8bef39 | |||
| c518fdc155 | |||
| ab34b174f9 | |||
| 6ac3bd4c7c | |||
| b4a8c227cc | |||
| 01c6b56685 | |||
| 5a81cf6c17 | |||
| eeb7524c0f | |||
| 2fd25f0093 | |||
| 8047f902a5 | |||
| 788a0154d3 | |||
| a8f4b0227f | |||
| cdf8c0fac9 | |||
| 8b5f71fb55 | |||
| c100df412d | |||
| cf00775bb8 | |||
| 97be3c05e6 | |||
| 4fd5552d3d | |||
| 12800f831a | |||
| e8a209c9b5 | |||
| 781fed1079 | |||
| ebb373eedb | |||
| 9f75bda8be | |||
| 370589d6c0 | |||
| 262c341bdc | |||
| 22b11d7d4d | |||
| 92f5f8dced | |||
| 034244217f | |||
| 5d9c3d595f | |||
| ed62822d44 | |||
| cf750c53fd | |||
| d2421d964a | |||
| 52e2091f2c | |||
| 1a29a769c6 | |||
| d66d25ff14 | |||
| d70ef90bdd | |||
| f772a5cf38 | |||
| 583c0f09a8 | |||
| 4d63b576f7 | |||
| 44cf66d7ff | |||
| 469c43ca25 | |||
| bb7dc0fd93 | |||
| b8baeac860 | |||
| a425ce0826 | |||
| 513aa61d17 | |||
| 6a55ec6955 | |||
| e470786fb9 | |||
| c3d88aa53d | |||
| cb5b6af9db | |||
| b188a02956 | |||
| 604e21e32c | |||
| 7dbebef3ff | |||
| f8a4ebd4a8 | |||
| 0117d9eedb | |||
| cdb23cafa6 | |||
| 727deaf1a6 | |||
| b7410ee718 | |||
| 583bfdf9bb | |||
| 41dcefb174 | |||
| 91bdb2f344 | |||
| 58b76bac0d | |||
| f8efee6c2a | |||
| 1666415620 | |||
| 7f8f5f3498 | |||
| a27d72b437 | |||
| 22126e99c1 | |||
| dc7f8014a2 | |||
| e112175abe | |||
| f7fd1f2a89 | |||
| ed1fe93648 | |||
| 616b8f82ee | |||
| 43896dc416 | |||
| 9b30634217 | |||
| 676e950fa3 | |||
| 90b86313db | |||
| 3c1e9e9a25 | |||
| b39aa02b52 | |||
| 860e771493 | |||
| e027770213 | |||
| 43ebc172cb | |||
| 892e79a982 | |||
| 542e080f0a | |||
| f35eb0f148 | |||
| c764e7fd97 | |||
| 1c97f10d8a | |||
| d8499e6941 | |||
| 105a678d64 | |||
| a12de09533 | |||
| 431a610f00 | |||
| 6e6ebc5947 | |||
| b30b78e442 | |||
| cab859a0e4 | |||
| a2c6469d41 | |||
| 55d95f9009 | |||
| ab1048b7ee | |||
| 38a10f8be4 | |||
| f165ba6480 | |||
| bcc875a39c | |||
| 2f2029a5c1 | |||
| 8dd509c31b | |||
| 171f6422b1 | |||
| 5e6c3f314c | |||
| efc97d177a | |||
| 601e90f5fe | |||
| decede394a | |||
| eb850ddd36 | |||
| 4a807b6373 | |||
| 6ed9164c9d | |||
| 0a13b7c559 | |||
| ef89dabb61 | |||
| 9be1ffd633 | |||
| 5e431149cd | |||
| 7023dedc05 | |||
| 9e98c86ae1 | |||
| 071237d322 | |||
| cb42cfc6af | |||
| 4fbe954a79 | |||
| 66621d762e | |||
| 9dd6aefcec | |||
| 749fd618a9 | |||
| a88176e060 | |||
| e06756bcab | |||
| 9c222a445f | |||
| 51bde36dee | |||
| e371e03696 | |||
| 0bf721a74c | |||
| 43b474143c | |||
| 02221c993e | |||
| 9c8d508247 | |||
| 8f42bec270 | |||
| 660d3fa1e9 | |||
| 1ef332e82c | |||
| cf926134ef |
+45
-1
@@ -1,4 +1,48 @@
|
||||
# 3.0.0 (unrelased master branch)
|
||||
# 3.0.0-beta5 (2016-04-15
|
||||
|
||||
### Bug fixes
|
||||
* **Grafna-cli**: Fixed issue grafana-cli tool, did not detect the right plugin dir, fixes [#4723](https://github.com/grafana/grafana/issues/4723)
|
||||
* **Graph**: Fixed issue with light theme text color issue in tooltip, fixes [#4702](https://github.com/grafana/grafana/issues/4702)
|
||||
* **Snapshot**: Fixed issue with empty snapshots, fixes [#4706](https://github.com/grafana/grafana/issues/4706)
|
||||
|
||||
# 3.0.0-beta4 (2016-04-13)
|
||||
|
||||
### Bug fixes
|
||||
* **Home dashboard**: Fixed issue with permission denied error on home dashboard, fixes [#4686](https://github.com/grafana/grafana/issues/4686)
|
||||
* **Templating**: Fixed issue templating variables that use regex extraction, fixes [#4672](https://github.com/grafana/grafana/issues/4672)
|
||||
|
||||
# 3.0.0-beta3 (2016-04-12)
|
||||
|
||||
### Enhancements
|
||||
* **InfluxDB**: Changed multi query encoding to work with InfluxDB 0.11 & 0.12, closes [#4533](https://github.com/grafana/grafana/issues/4533)
|
||||
* **Timepicker**: Add arrows and shortcuts for moving back and forth in current dashboard, closes [#119](https://github.com/grafana/grafana/issues/119)
|
||||
|
||||
### Bug fixes
|
||||
* **Postgres**: Fixed page render crash when using postgres, fixes [#4558](https://github.com/grafana/grafana/issues/4558)
|
||||
* **Table panel**: Fixed table panel bug when trying to show annotations in table panel, fixes [#4563](https://github.com/grafana/grafana/issues/4563)
|
||||
* **App Config**: Fixed app config issue showing content of other app config, fixes [#4575](https://github.com/grafana/grafana/issues/4575)
|
||||
* **Graph Panel**: Fixed legend option max not updating, fixes [#4601](https://github.com/grafana/grafana/issues/4601)
|
||||
* **Graph Panel**: Fixed issue where newly added graph panels shared same axes config, fixes [#4582](https://github.com/grafana/grafana/issues/4582)
|
||||
* **Graph Panel**: Fixed issue with axis labels overlapping Y-axis, fixes [#4626](https://github.com/grafana/grafana/issues/4626)
|
||||
* **InfluxDB**: Fixed issue with templating query containing template variable, fixes [#4602](https://github.com/grafana/grafana/issues/4602)
|
||||
* **Graph Panel**: Fixed issue with hiding series and stacking, fixes [#4557](https://github.com/grafana/grafana/issues/4557)
|
||||
* **Graph Panel**: Fixed issue with legend height in table mode with few series, affected iframe embedding as well, fixes [#4640](https://github.com/grafana/grafana/issues/4640)
|
||||
|
||||
# 3.0.0-beta2 (2016-04-04)
|
||||
|
||||
### 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 [#4541](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)
|
||||
|
||||
# 3.0.0-beta1 (2016-03-31)
|
||||
|
||||
### New Features
|
||||
* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
[Grafana](http://grafana.org) [](https://circleci.com/gh/grafana/grafana) [](https://coveralls.io/r/grafana/grafana) [](https://gitter.im/grafana/grafana?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[Grafana](http://grafana.org) [](https://circleci.com/gh/grafana/grafana) [](https://coveralls.io/r/grafana/grafana)
|
||||
================
|
||||
[Website](http://grafana.org) |
|
||||
[Twitter](https://twitter.com/grafana) |
|
||||
[IRC](https://webchat.freenode.net/?channels=grafana) |
|
||||

|
||||
[Slack](http://slack.raintank.io) |
|
||||
[Email](mailto:contact@grafana.org)
|
||||
|
||||
Grafana is an open source, feature rich metrics dashboard and graph editor for
|
||||
@@ -77,6 +79,7 @@ the latest master builds [here](http://grafana.org/download/builds)
|
||||
|
||||
- Go 1.5
|
||||
- NodeJS
|
||||
- [Godep](https://github.com/tools/godep)
|
||||
|
||||
### Get Code
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ os: Windows Server 2012 R2
|
||||
clone_folder: c:\gopath\src\github.com\grafana\grafana
|
||||
|
||||
environment:
|
||||
nodejs_version: "4"
|
||||
nodejs_version: "5"
|
||||
GOPATH: c:\gopath
|
||||
|
||||
install:
|
||||
|
||||
@@ -111,6 +111,13 @@ gc_interval_time = 86400
|
||||
# Change this option to false to disable reporting.
|
||||
reporting_enabled = true
|
||||
|
||||
# Set to false to disable all checks to https://grafana.net
|
||||
# for new vesions (grafana itself and plugins), check is used
|
||||
# in some UI views to notify that grafana or plugin update exists
|
||||
# This option does not cause any auto updates, nor send any information
|
||||
# only a GET request to http://grafana.net to get latest versions
|
||||
check_for_updates = true
|
||||
|
||||
# Google Analytics universal tracking code, only enabled if you specify an id here
|
||||
google_analytics_ua_id =
|
||||
|
||||
|
||||
@@ -100,6 +100,13 @@
|
||||
# Change this option to false to disable reporting.
|
||||
;reporting_enabled = true
|
||||
|
||||
# Set to false to disable all checks to https://grafana.net
|
||||
# for new vesions (grafana itself and plugins), check is used
|
||||
# in some UI views to notify that grafana or plugin update exists
|
||||
# This option does not cause any auto updates, nor send any information
|
||||
# only a GET request to http://grafana.net to get latest versions
|
||||
check_for_updates = true
|
||||
|
||||
# Google Analytics universal tracking code, only enabled if you specify an id here
|
||||
;google_analytics_ua_id =
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4.0.0
|
||||
3.0.0
|
||||
|
||||
@@ -207,35 +207,6 @@ page_keywords: grafana, admin, http, api, documentation, datasource
|
||||
|
||||
{"message":"Data source deleted"}
|
||||
|
||||
## Available data source types
|
||||
|
||||
`GET /api/datasources/plugins`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
GET /api/datasources/plugins HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"grafana":{
|
||||
"metrics":true,"module":"plugins/datasource/grafana/datasource",
|
||||
"name":"Grafana (for testing)",
|
||||
"partials":{
|
||||
"query":"app/plugins/datasource/grafana/partials/query.editor.html"
|
||||
},
|
||||
"pluginType":"datasource",
|
||||
"serviceName":"GrafanaDatasource",
|
||||
"type":"grafana"
|
||||
}
|
||||
}
|
||||
|
||||
## Data source proxy calls
|
||||
|
||||
`GET /api/datasources/proxy/:datasourceId/*`
|
||||
|
||||
@@ -10,14 +10,21 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
||||
|
||||
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-beta51460725904_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb)
|
||||
|
||||
## Install
|
||||
## Install Stable
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_2.6.0_amd64.deb
|
||||
|
||||
## Install 3.0 Beta
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_3.0.0-beta51460725904_amd64.deb
|
||||
|
||||
## APT Repository
|
||||
|
||||
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
|
||||
------------ | -------------
|
||||
.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-beta51460725904.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta51460725904§.x86_64.rpm)
|
||||
|
||||
## Install from package file
|
||||
## Install Stable Release from package file
|
||||
|
||||
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
|
||||
|
||||
## 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-beta51460725904.x86_64.rpm
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-3.0.0-beta51460725904.x86_64.rpm
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-3.0.0-beta51460725904.x86_64.rpm
|
||||
|
||||
|
||||
## Install via YUM Repository
|
||||
|
||||
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
|
||||
------------ | -------------
|
||||
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.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
||||
|
||||
## Configure
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ From grafana 3.0 it's very easy to develop your own plugins and share them with
|
||||
|
||||
## Short version
|
||||
|
||||
1. [Setup grafana](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md)
|
||||
1. [Setup grafana](http://docs.grafana.org/project/building_from_source/)
|
||||
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!
|
||||
|
||||
@@ -34,7 +34,7 @@ and [apps](./apps.md) plugins in the documentation.
|
||||
## Start developing your 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.
|
||||
1. Setup a Grafana development environment. [(described here)](http://docs.grafana.org/project/building_from_source/) 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.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ page_keywords: grafana, plugins, documentation
|
||||
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.
|
||||
bundling of datasources, panels, dashboards and Grafana pages into a cohesive experience.
|
||||
|
||||
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
|
||||
|
||||
@@ -28,14 +28,14 @@ List installed plugins
|
||||
grafana-cli plugins ls
|
||||
```
|
||||
|
||||
Upgrade all installed plugins
|
||||
Update all installed plugins
|
||||
```
|
||||
grafana-cli plugins upgrade-all
|
||||
grafana-cli plugins update-all
|
||||
```
|
||||
|
||||
Upgrade one plugin
|
||||
Update one plugin
|
||||
```
|
||||
grafana-cli plugins upgrade <plugin-id>
|
||||
grafana-cli plugins update <plugin-id>
|
||||
```
|
||||
|
||||
Remove one plugin
|
||||
|
||||
@@ -7,7 +7,7 @@ page_keywords: grafana, plugins, documentation
|
||||
|
||||
# Panels
|
||||
|
||||
Panels are the main bulding block of dashboards.
|
||||
Panels are the main building blocks of dashboards.
|
||||
|
||||
## Panel development
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,6 @@ module.exports = function(config) {
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'vendor/npm/es6-shim/es6-shim.js',
|
||||
'vendor/npm/es6-promise/dist/es6-promise.js',
|
||||
'vendor/npm/systemjs/dist/system.src.js',
|
||||
'test/test-main.js',
|
||||
|
||||
@@ -25,9 +24,10 @@ module.exports = function(config) {
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['PhantomJS'],
|
||||
captureTimeout: 2000,
|
||||
captureTimeout: 20000,
|
||||
singleRun: true,
|
||||
autoWatchBatchDelay: 1000,
|
||||
autoWatchBatchDelay: 10000,
|
||||
browserNoActivityTimeout: 60000,
|
||||
|
||||
});
|
||||
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"version": "2.1.1"
|
||||
"stable": "2.6.0",
|
||||
"testing": "3.0.0-beta4"
|
||||
}
|
||||
|
||||
+5
-5
@@ -4,13 +4,12 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "3.0.0-beta1",
|
||||
"version": "3.0.0-beta5",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular2": "2.0.0-beta.12",
|
||||
"zone.js": "^0.6.6",
|
||||
"autoprefixer": "^6.3.3",
|
||||
"es6-promise": "^3.0.2",
|
||||
@@ -32,7 +31,7 @@
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-filerev": "^0.2.1",
|
||||
"grunt-git-describe": "~2.3.2",
|
||||
"grunt-karma": "~0.12.1",
|
||||
"grunt-karma": "~0.12.2",
|
||||
"grunt-ng-annotate": "^1.0.1",
|
||||
"grunt-notify": "^0.4.3",
|
||||
"grunt-postcss": "^0.8.0",
|
||||
@@ -43,7 +42,7 @@
|
||||
"grunt-typescript": "^0.8.0",
|
||||
"grunt-usemin": "3.0.0",
|
||||
"jshint-stylish": "~2.1.0",
|
||||
"karma": "~0.13.15",
|
||||
"karma": "0.13.22",
|
||||
"karma-chrome-launcher": "~0.2.2",
|
||||
"karma-coverage": "0.5.3",
|
||||
"karma-coveralls": "1.1.2",
|
||||
@@ -54,7 +53,7 @@
|
||||
"mocha": "2.3.4",
|
||||
"phantomjs-prebuilt": "^2.1.3",
|
||||
"reflect-metadata": "0.1.2",
|
||||
"rxjs": "5.0.0-beta.2",
|
||||
"rxjs": "5.0.0-beta.4",
|
||||
"sass-lint": "^1.5.0",
|
||||
"systemjs": "0.19.24"
|
||||
},
|
||||
@@ -68,6 +67,7 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^1.2.0",
|
||||
"grunt-jscs": "~1.5.x",
|
||||
"grunt-sass-lint": "^0.1.0",
|
||||
"grunt-sync": "^0.4.1",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
deb_ver=3.0.0-pre1459399258
|
||||
rpm_ver=3.0.0-pre1459399258
|
||||
deb_ver=3.0.0-beta41460581169
|
||||
rpm_ver=3.0.0-beta41460581169
|
||||
|
||||
#rpm_ver=3.0.0-1
|
||||
|
||||
#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
|
||||
wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
|
||||
|
||||
# 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/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/debian/jessie grafana_${deb_ver}_amd64.deb
|
||||
package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
|
||||
@@ -16,7 +16,7 @@ package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
|
||||
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
|
||||
ackage_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
|
||||
|
||||
+21
-3
@@ -29,6 +29,8 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
// authed views
|
||||
r.Get("/profile/", reqSignedIn, Index)
|
||||
r.Get("/profile/password", reqSignedIn, Index)
|
||||
r.Get("/profile/switch-org/:id", reqSignedIn, ChangeActiveOrgAndRedirectToHome)
|
||||
r.Get("/org/", reqSignedIn, Index)
|
||||
r.Get("/org/new", reqSignedIn, Index)
|
||||
r.Get("/datasources/", reqSignedIn, Index)
|
||||
@@ -96,10 +98,15 @@ func Register(r *macaron.Macaron) {
|
||||
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||
r.Post("/using/:id", wrap(UserSetUsingOrg))
|
||||
r.Get("/orgs", wrap(GetSignedInUserOrgList))
|
||||
|
||||
r.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
||||
r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
||||
|
||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
||||
r.Get("/quotas", wrap(GetUserQuotas))
|
||||
|
||||
r.Get("/preferences", wrap(GetUserPreferences))
|
||||
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
|
||||
})
|
||||
|
||||
// users (admin permission required)
|
||||
@@ -130,6 +137,9 @@ func Register(r *macaron.Macaron) {
|
||||
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||
|
||||
// prefs
|
||||
r.Get("/preferences", wrap(GetOrgPreferences))
|
||||
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// create new org
|
||||
@@ -164,6 +174,11 @@ func Register(r *macaron.Macaron) {
|
||||
r.Delete("/:id", wrap(DeleteApiKey))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// Preferences
|
||||
r.Group("/preferences", func() {
|
||||
r.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
|
||||
})
|
||||
|
||||
// Data sources
|
||||
r.Group("/datasources", func() {
|
||||
r.Get("/", GetDataSources)
|
||||
@@ -176,12 +191,12 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
|
||||
|
||||
r.Group("/plugins", func() {
|
||||
r.Get("/", wrap(GetPluginList))
|
||||
r.Get("/plugins", wrap(GetPluginList))
|
||||
r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
|
||||
|
||||
r.Group("/plugins", func() {
|
||||
r.Get("/:pluginId/readme", wrap(GetPluginReadme))
|
||||
r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
|
||||
r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
|
||||
r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
@@ -238,6 +253,9 @@ func Register(r *macaron.Macaron) {
|
||||
// rendering
|
||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||
|
||||
// grafana.net proxy
|
||||
r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
|
||||
|
||||
// Gravatar service.
|
||||
avt := avatar.CacheServer()
|
||||
r.Get("/avatar/:hash", avt.ServeHTTP)
|
||||
|
||||
@@ -116,6 +116,7 @@ func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if avatar.Expired() {
|
||||
if err := avatar.Update(); err != nil {
|
||||
log.Trace("avatar update error: %v", err)
|
||||
avatar = this.notFound
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,24 @@ func canEditDashboard(role m.RoleType) bool {
|
||||
}
|
||||
|
||||
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")
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
|
||||
@@ -33,6 +33,7 @@ type CurrentUser struct {
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
type DashboardMeta struct {
|
||||
@@ -57,6 +58,10 @@ type DashboardFullWithMeta struct {
|
||||
Dashboard *simplejson.Json `json:"dashboard"`
|
||||
}
|
||||
|
||||
type DashboardRedirect struct {
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
}
|
||||
|
||||
type DataSource struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
|
||||
+11
-6
@@ -15,15 +15,20 @@ type PluginSetting struct {
|
||||
Dependencies *plugins.PluginDependencies `json:"dependencies"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
DefaultNavUrl string `json:"defaultNavUrl"`
|
||||
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
}
|
||||
|
||||
type PluginListItem struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
Info *plugins.PluginInfo `json:"info"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
Info *plugins.PluginInfo `json:"info"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
}
|
||||
|
||||
type PluginList []PluginListItem
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
if len(ds.JsonData.MustMap()) > 0 {
|
||||
if ds.JsonData != nil {
|
||||
dsMap["jsonData"] = ds.JsonData
|
||||
}
|
||||
|
||||
@@ -137,9 +137,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
"authProxyEnabled": setting.AuthProxyEnabled,
|
||||
"buildInfo": map[string]interface{}{
|
||||
"version": setting.BuildVersion,
|
||||
"commit": setting.BuildCommit,
|
||||
"buildstamp": setting.BuildStamp,
|
||||
"version": setting.BuildVersion,
|
||||
"commit": setting.BuildCommit,
|
||||
"buildstamp": setting.BuildStamp,
|
||||
"latestVersion": plugins.GrafanaLatestVersion,
|
||||
"hasUpdate": plugins.GrafanaHasUpdate,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var gNetProxyTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy {
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = "grafana.net"
|
||||
req.Host = "grafana.net"
|
||||
|
||||
req.URL.Path = util.JoinUrlFragments("https://grafana.net/api", proxyPath)
|
||||
|
||||
// clear cookie headers
|
||||
req.Header.Del("Cookie")
|
||||
req.Header.Del("Set-Cookie")
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{Director: director}
|
||||
}
|
||||
|
||||
func ProxyGnetRequest(c *middleware.Context) {
|
||||
proxyPath := c.Params("*")
|
||||
proxy := ReverseProxyGnetReq(proxyPath)
|
||||
proxy.Transport = gNetProxyTransport
|
||||
proxy.ServeHTTP(c.Resp, c.Req.Request)
|
||||
c.Resp.Header().Del("Set-Cookie")
|
||||
}
|
||||
+9
-1
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
@@ -14,6 +15,12 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
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{
|
||||
User: &dtos.CurrentUser{
|
||||
Id: c.UserId,
|
||||
@@ -21,12 +28,13 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
Login: c.Login,
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
LightTheme: c.Theme == "light",
|
||||
OrgId: c.OrgId,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
LightTheme: prefs.Theme == "light",
|
||||
Timezone: prefs.Timezone,
|
||||
},
|
||||
Settings: settings,
|
||||
AppUrl: setting.AppUrl,
|
||||
|
||||
+14
-4
@@ -14,6 +14,7 @@ func GetPluginList(c *middleware.Context) Response {
|
||||
typeFilter := c.Query("type")
|
||||
enabledFilter := c.Query("enabled")
|
||||
embeddedFilter := c.Query("embedded")
|
||||
coreFilter := c.Query("core")
|
||||
|
||||
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
|
||||
|
||||
@@ -28,16 +29,23 @@ func GetPluginList(c *middleware.Context) Response {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter out core plugins
|
||||
if coreFilter == "0" && pluginDef.IsCorePlugin {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter on type
|
||||
if typeFilter != "" && typeFilter != pluginDef.Type {
|
||||
continue
|
||||
}
|
||||
|
||||
listItem := dtos.PluginListItem{
|
||||
Id: pluginDef.Id,
|
||||
Name: pluginDef.Name,
|
||||
Type: pluginDef.Type,
|
||||
Info: &pluginDef.Info,
|
||||
Id: pluginDef.Id,
|
||||
Name: pluginDef.Name,
|
||||
Type: pluginDef.Type,
|
||||
Info: &pluginDef.Info,
|
||||
LatestVersion: pluginDef.GrafanaNetVersion,
|
||||
HasUpdate: pluginDef.GrafanaNetHasUpdate,
|
||||
}
|
||||
|
||||
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
|
||||
@@ -81,6 +89,8 @@ func GetPluginSettingById(c *middleware.Context) Response {
|
||||
BaseUrl: def.BaseUrl,
|
||||
Module: def.Module,
|
||||
DefaultNavUrl: def.DefaultNavUrl,
|
||||
LatestVersion: def.GrafanaNetVersion,
|
||||
HasUpdate: def.GrafanaNetHasUpdate,
|
||||
}
|
||||
|
||||
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -109,6 +110,23 @@ func UserSetUsingOrg(c *middleware.Context) Response {
|
||||
return ApiSuccess("Active organization changed")
|
||||
}
|
||||
|
||||
// GET /profile/switch-org/:id
|
||||
func ChangeActiveOrgAndRedirectToHome(c *middleware.Context) {
|
||||
orgId := c.ParamsInt64(":id")
|
||||
|
||||
if !validateUsingOrg(c.UserId, orgId) {
|
||||
NotFoundHandler(c)
|
||||
}
|
||||
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
NotFoundHandler(c)
|
||||
}
|
||||
|
||||
c.Redirect(setting.AppSubUrl + "/")
|
||||
}
|
||||
|
||||
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) Response {
|
||||
userQuery := m.GetUserByIdQuery{Id: c.UserId}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
|
||||
@@ -25,27 +26,33 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
|
||||
var pluginCommands = []cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "install <plugin name>",
|
||||
Usage: "install <plugin id>",
|
||||
Action: runCommand(installCommand),
|
||||
}, {
|
||||
Name: "list-remote",
|
||||
Usage: "list remote available plugins",
|
||||
Action: runCommand(listremoteCommand),
|
||||
}, {
|
||||
Name: "upgrade",
|
||||
Usage: "upgrade <plugin name>",
|
||||
Action: runCommand(upgradeCommand),
|
||||
Name: "update",
|
||||
Usage: "update <plugin id>",
|
||||
Aliases: []string{"upgrade"},
|
||||
Action: runCommand(upgradeCommand),
|
||||
}, {
|
||||
Name: "upgrade-all",
|
||||
Usage: "upgrades all your installed plugins",
|
||||
Action: runCommand(upgradeAllCommand),
|
||||
Name: "update-all",
|
||||
Aliases: []string{"upgrade-all"},
|
||||
Usage: "update all your installed plugins",
|
||||
Action: runCommand(upgradeAllCommand),
|
||||
}, {
|
||||
Name: "ls",
|
||||
Usage: "list all installed plugins",
|
||||
Action: runCommand(lsCommand),
|
||||
}, {
|
||||
Name: "uninstall",
|
||||
Usage: "uninstall <plugin id>",
|
||||
Action: runCommand(removeCommand),
|
||||
}, {
|
||||
Name: "remove",
|
||||
Usage: "remove <plugin name>",
|
||||
Usage: "remove <plugin id>",
|
||||
Action: runCommand(removeCommand),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ func RemoveGitBuildFromName(pluginName, filename string) string {
|
||||
}
|
||||
|
||||
var retryCount = 0
|
||||
var permissionsDeniedMessage = "Could not create %s. Permission denied. Make sure you have write access to plugindir"
|
||||
|
||||
func downloadFile(pluginName, filePath, url string) (err error) {
|
||||
defer func() {
|
||||
@@ -153,16 +154,16 @@ func downloadFile(pluginName, filePath, url string) (err error) {
|
||||
newFile := path.Join(filePath, RemoveGitBuildFromName(pluginName, zf.Name))
|
||||
|
||||
if zf.FileInfo().IsDir() {
|
||||
os.Mkdir(newFile, 0777)
|
||||
err := os.Mkdir(newFile, 0777)
|
||||
if PermissionsError(err) {
|
||||
return fmt.Errorf(permissionsDeniedMessage, newFile)
|
||||
}
|
||||
} else {
|
||||
dst, err := os.Create(newFile)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "permission denied") {
|
||||
return fmt.Errorf(
|
||||
"Could not create file %s. permission deined. Make sure you have write access to plugindir",
|
||||
newFile)
|
||||
}
|
||||
if PermissionsError(err) {
|
||||
return fmt.Errorf(permissionsDeniedMessage, newFile)
|
||||
}
|
||||
|
||||
defer dst.Close()
|
||||
src, err := zf.Open()
|
||||
if err != nil {
|
||||
@@ -176,3 +177,7 @@ func downloadFile(pluginName, filePath, url string) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PermissionsError(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "permission denied")
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func upgradeAllCommand(c CommandLine) error {
|
||||
}
|
||||
|
||||
for _, p := range pluginsToUpgrade {
|
||||
log.Infof("Upgrading %v \n", p.Id)
|
||||
log.Infof("Updating %v \n", p.Id)
|
||||
|
||||
s.RemoveInstalledPlugin(pluginsDir, p.Id)
|
||||
InstallPlugin(p.Id, "", c)
|
||||
|
||||
@@ -8,17 +8,37 @@ import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version = "master"
|
||||
|
||||
func getGrafanaPluginDir() string {
|
||||
os := runtime.GOOS
|
||||
if os == "windows" {
|
||||
currentOS := runtime.GOOS
|
||||
defaultNix := "/var/lib/grafana/plugins"
|
||||
|
||||
if currentOS == "windows" {
|
||||
return "C:\\opt\\grafana\\plugins"
|
||||
} else {
|
||||
return "/var/lib/grafana/plugins"
|
||||
}
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
|
||||
if err != nil {
|
||||
log.Error("Could not get current path. using default")
|
||||
return defaultNix
|
||||
}
|
||||
|
||||
if isDevenvironment(pwd) {
|
||||
return "../../../data/plugins"
|
||||
}
|
||||
|
||||
return defaultNix
|
||||
}
|
||||
|
||||
func isDevenvironment(pwd string) bool {
|
||||
// if grafana-cli is executed from the cmd folder we can assume
|
||||
// that its in development environment.
|
||||
return strings.HasSuffix(pwd, "/pkg/cmd/grafana-cli")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
)
|
||||
|
||||
var version = "3.0.0-pre1"
|
||||
var version = "3.0.0-beta4"
|
||||
var commit = "NA"
|
||||
var buildstamp string
|
||||
var build_date string
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -56,6 +57,9 @@ func sendUsageStats() {
|
||||
metrics["stats.users.count"] = statsQuery.Result.UserCount
|
||||
metrics["stats.orgs.count"] = statsQuery.Result.OrgCount
|
||||
metrics["stats.playlist.count"] = statsQuery.Result.PlaylistCount
|
||||
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
|
||||
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
|
||||
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
|
||||
|
||||
dsStats := m.GetDataSourceStatsQuery{}
|
||||
if err := bus.Dispatch(&dsStats); err != nil {
|
||||
|
||||
@@ -148,3 +148,8 @@ type GetDashboardsQuery struct {
|
||||
DashboardIds []int64
|
||||
Result *[]Dashboard
|
||||
}
|
||||
|
||||
type GetDashboardSlugByIdQuery struct {
|
||||
Id int64
|
||||
Result string
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
+8
-9
@@ -21,15 +21,14 @@ type GetDataSourceStatsQuery struct {
|
||||
}
|
||||
|
||||
type AdminStats struct {
|
||||
UserCount int `json:"user_count"`
|
||||
OrgCount int `json:"org_count"`
|
||||
DashboardCount int `json:"dashboard_count"`
|
||||
DbSnapshotCount int `json:"db_snapshot_count"`
|
||||
DbTagCount int `json:"db_tag_count"`
|
||||
DataSourceCount int `json:"data_source_count"`
|
||||
PlaylistCount int `json:"playlist_count"`
|
||||
StarredDbCount int `json:"starred_db_count"`
|
||||
GrafanaAdminCount int `json:"grafana_admin_count"`
|
||||
UserCount int `json:"user_count"`
|
||||
OrgCount int `json:"org_count"`
|
||||
DashboardCount int `json:"dashboard_count"`
|
||||
DbSnapshotCount int `json:"db_snapshot_count"`
|
||||
DbTagCount int `json:"db_tag_count"`
|
||||
DataSourceCount int `json:"data_source_count"`
|
||||
PlaylistCount int `json:"playlist_count"`
|
||||
StarredDbCount int `json:"starred_db_count"`
|
||||
}
|
||||
|
||||
type GetAdminStatsQuery struct {
|
||||
|
||||
@@ -136,7 +136,6 @@ type SignedInUser struct {
|
||||
Login string
|
||||
Name string
|
||||
Email string
|
||||
Theme string
|
||||
ApiKeyId int64
|
||||
IsGrafanaAdmin bool
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type FrontendPluginBase struct {
|
||||
}
|
||||
|
||||
func (fp *FrontendPluginBase) initFrontendPlugin() {
|
||||
if isInternalPlugin(fp.PluginDir) {
|
||||
if isExternalPlugin(fp.PluginDir) {
|
||||
StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
|
||||
Directory: fp.PluginDir,
|
||||
PluginId: fp.Id,
|
||||
@@ -48,17 +48,18 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
|
||||
|
||||
func (fp *FrontendPluginBase) handleModuleDefaults() {
|
||||
|
||||
if isInternalPlugin(fp.PluginDir) {
|
||||
if isExternalPlugin(fp.PluginDir) {
|
||||
fp.Module = path.Join("plugins", fp.Id, "module")
|
||||
fp.BaseUrl = path.Join("public/plugins", fp.Id)
|
||||
return
|
||||
}
|
||||
|
||||
fp.IsCorePlugin = true
|
||||
fp.Module = path.Join("app/plugins", fp.Type, fp.Id, "module")
|
||||
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, fp.Id)
|
||||
}
|
||||
|
||||
func isInternalPlugin(pluginDir string) bool {
|
||||
func isExternalPlugin(pluginDir string) bool {
|
||||
return !strings.Contains(pluginDir, setting.StaticRootPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ type PluginBase struct {
|
||||
IncludedInAppId string `json:"-"`
|
||||
PluginDir string `json:"-"`
|
||||
DefaultNavUrl string `json:"-"`
|
||||
IsCorePlugin bool `json:"-"`
|
||||
|
||||
GrafanaNetVersion string `json:"-"`
|
||||
GrafanaNetHasUpdate bool `json:"-"`
|
||||
|
||||
// cache for readme file contents
|
||||
Readme []byte `json:"-"`
|
||||
|
||||
@@ -22,6 +22,9 @@ var (
|
||||
Apps map[string]*AppPlugin
|
||||
Plugins map[string]*PluginBase
|
||||
PluginTypes map[string]interface{}
|
||||
|
||||
GrafanaLatestVersion string
|
||||
GrafanaHasUpdate bool
|
||||
)
|
||||
|
||||
type PluginScanner struct {
|
||||
@@ -70,6 +73,7 @@ func Init() error {
|
||||
app.initApp()
|
||||
}
|
||||
|
||||
go StartPluginUpdateChecker()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type GrafanaNetPlugin struct {
|
||||
Slug string `json:"slug"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type GithubLatest struct {
|
||||
Stable string `json:"stable"`
|
||||
Testing string `json:"testing"`
|
||||
}
|
||||
|
||||
func StartPluginUpdateChecker() {
|
||||
if !setting.CheckForUpdates {
|
||||
return
|
||||
}
|
||||
|
||||
// do one check directly
|
||||
go checkForUpdates()
|
||||
|
||||
ticker := time.NewTicker(time.Minute * 10)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
checkForUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAllExternalPluginSlugs() string {
|
||||
str := ""
|
||||
|
||||
for _, plug := range Plugins {
|
||||
if plug.IsCorePlugin {
|
||||
continue
|
||||
}
|
||||
|
||||
str += plug.Id + ","
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func checkForUpdates() {
|
||||
log.Trace("Checking for updates")
|
||||
|
||||
client := http.Client{Timeout: time.Duration(5 * time.Second)}
|
||||
|
||||
pluginSlugs := getAllExternalPluginSlugs()
|
||||
resp, err := client.Get("https://grafana.net/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion)
|
||||
|
||||
if err != nil {
|
||||
log.Trace("Failed to get plugins repo from grafana.net, %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Trace("Update check failed, reading response from grafana.net, %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
gNetPlugins := []GrafanaNetPlugin{}
|
||||
err = json.Unmarshal(body, &gNetPlugins)
|
||||
if err != nil {
|
||||
log.Trace("Failed to unmarshal plugin repo, reading response from grafana.net, %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, plug := range Plugins {
|
||||
for _, gplug := range gNetPlugins {
|
||||
if gplug.Slug == plug.Id {
|
||||
plug.GrafanaNetVersion = gplug.Version
|
||||
plug.GrafanaNetHasUpdate = plug.Info.Version != plug.GrafanaNetVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json")
|
||||
if err != nil {
|
||||
log.Trace("Failed to get lates.json repo from github: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer resp2.Body.Close()
|
||||
body, err = ioutil.ReadAll(resp2.Body)
|
||||
if err != nil {
|
||||
log.Trace("Update check failed, reading response from github.net, %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var githubLatest GithubLatest
|
||||
err = json.Unmarshal(body, &githubLatest)
|
||||
if err != nil {
|
||||
log.Trace("Failed to unmarshal github latest, reading response from github: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(setting.BuildVersion, "-") {
|
||||
GrafanaLatestVersion = githubLatest.Testing
|
||||
GrafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, githubLatest.Testing)
|
||||
} else {
|
||||
GrafanaLatestVersion = githubLatest.Stable
|
||||
GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ func init() {
|
||||
bus.AddHandler("sql", DeleteDashboard)
|
||||
bus.AddHandler("sql", SearchDashboards)
|
||||
bus.AddHandler("sql", GetDashboardTags)
|
||||
bus.AddHandler("sql", GetDashboardSlugById)
|
||||
}
|
||||
|
||||
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
@@ -255,3 +256,23 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
|
||||
|
||||
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,29 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addPreferencesMigrations(mg *Migrator) {
|
||||
|
||||
preferencesV1 := Table{
|
||||
mg.AddMigration("drop preferences table v2", NewDropTableMigration("preferences"))
|
||||
|
||||
preferencesV2 := Table{
|
||||
Name: "preferences",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "pref_id", Type: DB_Int, Nullable: false},
|
||||
{Name: "pref_type", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "pref_data", Type: DB_Text, Nullable: false},
|
||||
{Name: "org_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "version", Type: DB_Int, Nullable: false},
|
||||
{Name: "home_dashboard_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: false},
|
||||
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: false},
|
||||
{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
|
||||
mg.AddMigration("create preferences table v1", NewAddTableMigration(preferencesV1))
|
||||
mg.AddMigration("drop preferences table v3", NewDropTableMigration("preferences"))
|
||||
|
||||
// create table
|
||||
mg.AddMigration("create preferences table v3", NewAddTableMigration(preferencesV2))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -85,12 +85,7 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error {
|
||||
(
|
||||
SELECT COUNT(DISTINCT ` + dialect.Quote("dashboard_id") + ` )
|
||||
FROM ` + dialect.Quote("star") + `
|
||||
) AS starred_db_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("user") + `
|
||||
WHERE ` + dialect.Quote("is_admin") + ` = 1
|
||||
) AS grafana_admin_count
|
||||
) AS starred_db_count
|
||||
`
|
||||
|
||||
var stats m.AdminStats
|
||||
|
||||
@@ -273,7 +273,6 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
|
||||
u.email as email,
|
||||
u.login as login,
|
||||
u.name as name,
|
||||
u.theme as theme,
|
||||
org.name as org_name,
|
||||
org_user.role as org_role,
|
||||
org.id as org_id
|
||||
|
||||
@@ -124,6 +124,7 @@ var (
|
||||
appliedEnvOverrides []string
|
||||
|
||||
ReportingEnabled bool
|
||||
CheckForUpdates bool
|
||||
GoogleAnalyticsId string
|
||||
GoogleTagManagerId string
|
||||
|
||||
@@ -475,6 +476,7 @@ func NewConfigContext(args *CommandLineArgs) error {
|
||||
|
||||
analytics := Cfg.Section("analytics")
|
||||
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
||||
CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
|
||||
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
|
||||
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
|
||||
|
||||
|
||||
@@ -7,10 +7,6 @@ import coreModule from 'app/core/core_module';
|
||||
|
||||
var template = `
|
||||
<div class="graph-legend-popover">
|
||||
<a class="drop-popopver-close" ng-click="ctrl.close();" href="" ng-hide="ctrl.autoClose">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</a>
|
||||
|
||||
<div ng-show="ctrl.series" class="p-b-1">
|
||||
<label>Y Axis:</label>
|
||||
<button ng-click="ctrl.toggleAxis(yaxis);" class="btn btn-small"
|
||||
@@ -31,7 +27,6 @@ var template = `
|
||||
ng-style="{color:color}"
|
||||
ng-click="ctrl.colorSelected(color);"> </i>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -51,21 +46,17 @@ export class ColorPickerCtrl {
|
||||
toggleAxis(yaxis) {
|
||||
this.$scope.toggleAxis();
|
||||
|
||||
if (!this.$scope.autoClose) {
|
||||
if (this.$scope.autoClose) {
|
||||
this.$scope.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
colorSelected(color) {
|
||||
this.$scope.colorSelected(color);
|
||||
if (!this.$scope.autoClose) {
|
||||
if (this.$scope.autoClose) {
|
||||
this.$scope.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$scope.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
export function colorPicker() {
|
||||
|
||||
@@ -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);
|
||||
@@ -21,10 +21,6 @@
|
||||
<i class="{{::menuItem.icon}}" ng-show="::menuItem.icon"></i>
|
||||
{{::menuItem.text}}
|
||||
</a>
|
||||
<a ng-click="menuItem.click()" ng-show="::menuItem.click">
|
||||
<i class="{{::menuItem.icon}}"></i>
|
||||
{{::menuItem.text}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -72,9 +72,8 @@ export class SideMenuCtrl {
|
||||
this.orgMenu.push({
|
||||
text: "Switch to " + org.name,
|
||||
icon: "fa fa-fw fa-random",
|
||||
click: () => {
|
||||
this.switchOrg(org.orgId);
|
||||
}
|
||||
url: this.getUrl('/profile/switch-org/' + org.orgId),
|
||||
target: '_self'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,12 +82,6 @@ export class SideMenuCtrl {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switchOrg(orgId) {
|
||||
this.backendSrv.post('/api/user/using/' + orgId).then(() => {
|
||||
window.location.href = `${config.appSubUrl}/`;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function sideMenuDirective() {
|
||||
|
||||
@@ -21,17 +21,14 @@ export class SwitchCtrl {
|
||||
id: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope) {
|
||||
constructor($scope, private $timeout) {
|
||||
this.show = true;
|
||||
this.id = $scope.$id;
|
||||
}
|
||||
|
||||
internalOnChange() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
this.onChange();
|
||||
resolve();
|
||||
});
|
||||
return this.$timeout(() => {
|
||||
return this.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ function (angular, coreModule, config) {
|
||||
$scope.buildInfo = {
|
||||
version: config.buildInfo.version,
|
||||
commit: config.buildInfo.commit,
|
||||
buildstamp: new Date(config.buildInfo.buildstamp * 1000)
|
||||
buildstamp: new Date(config.buildInfo.buildstamp * 1000),
|
||||
latestVersion: config.buildInfo.latestVersion,
|
||||
hasUpdate: config.buildInfo.hasUpdate,
|
||||
};
|
||||
|
||||
$scope.submit = function() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import {liveSrv} from './live/live_srv';
|
||||
import {Emitter} from './utils/emitter';
|
||||
import {layoutSelector} from './components/layout_selector/layout_selector';
|
||||
import {switchDirective} from './components/switch';
|
||||
import {dashboardSelector} from './components/dashboard_selector';
|
||||
import 'app/core/controllers/all';
|
||||
import 'app/core/services/all';
|
||||
import 'app/core/routes/routes';
|
||||
@@ -54,4 +55,5 @@ export {
|
||||
infoPopover,
|
||||
Emitter,
|
||||
appEvents,
|
||||
dashboardSelector,
|
||||
};
|
||||
|
||||
@@ -169,7 +169,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
return System.import(model.module).then(function(appModule) {
|
||||
return {
|
||||
baseUrl: model.baseUrl,
|
||||
name: 'app-config-' + model.pluginId,
|
||||
name: 'app-config-' + model.id,
|
||||
bindings: {appModel: "=", appEditCtrl: "="},
|
||||
attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
|
||||
Component: appModule.ConfigCtrl,
|
||||
|
||||
@@ -4,13 +4,17 @@ define([
|
||||
function (coreModule) {
|
||||
"use strict";
|
||||
|
||||
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv) {
|
||||
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
|
||||
|
||||
if (!$routeParams.slug) {
|
||||
backendSrv.get('/api/dashboards/home').then(function(result) {
|
||||
var meta = result.meta;
|
||||
meta.canSave = meta.canShare = meta.canStar = false;
|
||||
$scope.initDashboard(result, $scope);
|
||||
backendSrv.get('/api/dashboards/home').then(function(homeDash) {
|
||||
if (homeDash.redirectUri) {
|
||||
$location.path('dashboard/' + homeDash.redirectUri);
|
||||
} else {
|
||||
var meta = homeDash.meta;
|
||||
meta.canSave = meta.canShare = meta.canStar = false;
|
||||
$scope.initDashboard(homeDash, $scope);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,16 +88,20 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
.when('/profile', {
|
||||
templateUrl: 'public/app/features/profile/partials/profile.html',
|
||||
templateUrl: 'public/app/features/org/partials/profile.html',
|
||||
controller : 'ProfileCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
.when('/profile/password', {
|
||||
templateUrl: 'public/app/features/profile/partials/password.html',
|
||||
templateUrl: 'public/app/features/org/partials/change_password.html',
|
||||
controller : 'ChangePasswordCtrl',
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
.when('/profile/select-org', {
|
||||
templateUrl: 'public/app/features/profile/partials/select_org.html',
|
||||
templateUrl: 'public/app/features/org/partials/select_org.html',
|
||||
controller : 'SelectOrgCtrl',
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -46,9 +46,12 @@ function popoverSrv($compile, $rootScope) {
|
||||
drop.on('close', () => {
|
||||
popoverScope.dismiss({fromDropClose: true});
|
||||
destroyDrop();
|
||||
if (options.onClose) {
|
||||
options.onClose();
|
||||
}
|
||||
});
|
||||
|
||||
drop.open();
|
||||
setTimeout(() => { drop.open(); }, 10);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class TimeSeries {
|
||||
fillBelowTo: any;
|
||||
transform: any;
|
||||
flotpairs: any;
|
||||
unit: any;
|
||||
|
||||
constructor(opts) {
|
||||
this.datapoints = opts.datapoints;
|
||||
@@ -52,6 +53,7 @@ export default class TimeSeries {
|
||||
this.valueFormater = kbn.valueFormats.none;
|
||||
this.stats = {};
|
||||
this.legend = true;
|
||||
this.unit = opts.unit;
|
||||
}
|
||||
|
||||
applySeriesOverrides(overrides) {
|
||||
@@ -170,7 +172,7 @@ export default class TimeSeries {
|
||||
}
|
||||
|
||||
isMsResolutionNeeded() {
|
||||
for (var i = 0; i<this.datapoints.length; i++) {
|
||||
for (var i = 0; i < this.datapoints.length; i++) {
|
||||
if (this.datapoints[i][0] !== null) {
|
||||
var timestamp = this.datapoints[i][0].toString();
|
||||
if (timestamp.length === 13 && (timestamp % 1000) !== 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import {Subject} from 'vendor/npm/rxjs/Subject';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
var hasOwnProp = {}.hasOwnProperty;
|
||||
|
||||
@@ -9,48 +9,27 @@ function createName(name) {
|
||||
}
|
||||
|
||||
export class Emitter {
|
||||
subjects: any;
|
||||
emitter: any;
|
||||
|
||||
constructor() {
|
||||
this.subjects = {};
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
|
||||
emit(name, data?) {
|
||||
var fnName = createName(name);
|
||||
this.subjects[fnName] || (this.subjects[fnName] = new Subject());
|
||||
this.subjects[fnName].next(data);
|
||||
this.emitter.emit(name, data);
|
||||
}
|
||||
|
||||
on(name, handler, scope?) {
|
||||
var fnName = createName(name);
|
||||
this.subjects[fnName] || (this.subjects[fnName] = new Subject());
|
||||
var subscription = this.subjects[fnName].subscribe(handler);
|
||||
this.emitter.on(name, handler);
|
||||
|
||||
if (scope) {
|
||||
scope.$on('$destroy', function() {
|
||||
subscription.unsubscribe();
|
||||
this.emitter.off(name, handler);
|
||||
});
|
||||
}
|
||||
|
||||
return subscription;
|
||||
};
|
||||
}
|
||||
|
||||
off(name, handler) {
|
||||
var fnName = createName(name);
|
||||
if (this.subjects[fnName]) {
|
||||
this.subjects[fnName].dispose();
|
||||
delete this.subjects[fnName];
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
var subjects = this.subjects;
|
||||
for (var prop in subjects) {
|
||||
if (hasOwnProp.call(subjects, prop)) {
|
||||
subjects[prop].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
this.subjects = {};
|
||||
this.emitter.off(name, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
<td>Total users</td>
|
||||
<td>{{ctrl.stats.user_count}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total grafana admins</td>
|
||||
<td>{{ctrl.stats.grafana_admin_count}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total organizations</td>
|
||||
<td>{{ctrl.stats.org_count}}</td>
|
||||
|
||||
@@ -7,8 +7,5 @@ define([
|
||||
'./playlist/all',
|
||||
'./snapshot/all',
|
||||
'./panel/all',
|
||||
'./profile/profileCtrl',
|
||||
'./profile/changePasswordCtrl',
|
||||
'./profile/selectOrgCtrl',
|
||||
'./styleguide/styleguide',
|
||||
], function () {});
|
||||
|
||||
@@ -45,6 +45,7 @@ function (angular, _, $) {
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.currentAnnotation = angular.copy(annotationDefaults);
|
||||
$scope.currentAnnotation.datasource = $scope.datasources[0].name;
|
||||
$scope.currentIsNew = true;
|
||||
$scope.datasourceChanged();
|
||||
};
|
||||
|
||||
@@ -134,6 +134,10 @@ function (angular, $, config, moment) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.timezoneChanged = function() {
|
||||
$rootScope.$broadcast("refresh");
|
||||
};
|
||||
|
||||
$scope.formatDate = function(date) {
|
||||
return moment(date).format('MMM Do YYYY, h:mm:ss a');
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ function (angular, $, _, moment) {
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('dashboardSrv', function() {
|
||||
module.factory('dashboardSrv', function(contextSrv) {
|
||||
|
||||
function DashboardModel (data, meta) {
|
||||
if (!data) {
|
||||
@@ -25,7 +25,7 @@ function (angular, $, _, moment) {
|
||||
this.originalTitle = this.title;
|
||||
this.tags = data.tags || [];
|
||||
this.style = data.style || "dark";
|
||||
this.timezone = data.timezone || 'browser';
|
||||
this.timezone = data.timezone || '';
|
||||
this.editable = data.editable !== false;
|
||||
this.hideControls = data.hideControls || 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) {
|
||||
var i, j, k;
|
||||
var oldVersion = this.schemaVersion;
|
||||
@@ -420,6 +428,8 @@ function (angular, $, _, moment) {
|
||||
// update graph yaxes changes
|
||||
panelUpgrades.push(function(panel) {
|
||||
if (panel.type !== 'graph') { return; }
|
||||
if (!panel.grid) { return; }
|
||||
|
||||
if (!panel.yaxes) {
|
||||
panel.yaxes = [
|
||||
{
|
||||
|
||||
@@ -60,6 +60,14 @@ function(angular, $) {
|
||||
scope.appEvent('zoom-out', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('left', function(evt) {
|
||||
scope.appEvent('shift-time-backward', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('right', function(evt) {
|
||||
scope.appEvent('shift-time-forward', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+e', function(evt) {
|
||||
scope.appEvent('export-dashboard', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Timezone</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f for f in ['browser','utc']"></select>
|
||||
<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>
|
||||
@@ -62,66 +62,67 @@
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 1">
|
||||
<h5 class="section-heading">Rows settings</h5>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Title</span>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
|
||||
<div ng-if="editor.index == 1">
|
||||
<h5 class="section-heading">Rows settings</h5>
|
||||
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
|
||||
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-inverse 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 class="gf-form-group">
|
||||
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Title</span>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
|
||||
|
||||
<div ng-if="editor.index == 2">
|
||||
<dash-links-editor></dash-links-editor>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
|
||||
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-inverse 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">
|
||||
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
|
||||
</div>
|
||||
<div ng-if="editor.index == 2">
|
||||
<dash-links-editor></dash-links-editor>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 4">
|
||||
<h5 class="section-heading">Dashboard info</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Last updated at:</span>
|
||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Last updated by:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Created at:</span>
|
||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Created by:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Current version:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.version}} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="editor.index == 3">
|
||||
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 4">
|
||||
<h5 class="section-heading">Dashboard info</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Last updated at:</span>
|
||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Last updated by:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Created at:</span>
|
||||
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Created by:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}} </span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Current version:</span>
|
||||
<span class="gf-form-label width-18">{{dashboardMeta.version}} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,31 +38,26 @@
|
||||
|
||||
<div ng-include src="'shareLinkOptions.html'"></div>
|
||||
|
||||
<div class="gf-form-group position-center">
|
||||
<div class="gf-form width-30" >
|
||||
<div class="gf-form-group section">
|
||||
<div class="gf-form width-30">
|
||||
<textarea rows="5" data-share-panel-url class="gf-form-input width-30" ng-model='iframeHtml'></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form position-center">
|
||||
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="shareLinkOptions.html">
|
||||
<div class="gf-form-group position-center">
|
||||
<div class="gf-form-group section">
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Current time range" label-class="width-12" switch-class="max-width-6"
|
||||
checked="options.forCurrent" on-change="buildUrl()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Template variables" label-class="width-12" switch-class="max-width-6"
|
||||
checked="options.includeTemplateVars" on-change="buildUrl()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-5">Include</span>
|
||||
<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-5">Include</span>
|
||||
<editor-checkbox text="Template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-5">Theme</span>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<span class="gf-form-label width-12">Theme</span>
|
||||
<div class="gf-form-select-wrapper width-6">
|
||||
<select class="gf-form-input" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,18 +70,19 @@
|
||||
</div>
|
||||
|
||||
<div ng-include src="'shareLinkOptions.html'"></div>
|
||||
<div class="gf-form-group position-center">
|
||||
<div class="gf-form-inline">
|
||||
|
||||
<div class="gf-form width-30">
|
||||
<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
|
||||
</div>
|
||||
<div class="gf-form pull-right">
|
||||
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
|
||||
<div>
|
||||
<div class="gf-form-group section">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form width-30">
|
||||
<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
|
||||
</div>
|
||||
<div class="gf-form pull-right">
|
||||
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form position-center" ng-show="modeSharePanel">
|
||||
<div class="gf-form section" ng-show="modeSharePanel">
|
||||
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
|
||||
</div>
|
||||
</script>
|
||||
@@ -117,7 +113,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group share-modal-options position-center">
|
||||
<div class="gf-form-group share-modal-options">
|
||||
<div class="gf-form" ng-if="step === 1">
|
||||
<span class="gf-form-label width-12">Snapshot name</span>
|
||||
<input type="text" ng-model="snapshot.name" class="gf-form-input max-width-15" >
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<ul class="nav gf-timepicker-nav">
|
||||
|
||||
<li class="dashnav-zoom-out" style="padding-top: 2px">
|
||||
<a class='small' ng-click='ctrl.zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(-1)'><i class="fa fa-chevron-left"></i></a>
|
||||
</li>
|
||||
<li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom in <br> CTRL+Z'" data-placement="bottom">
|
||||
<a ng-click='ctrl.zoom(2)'>Zoom Out</a></li>
|
||||
</li>
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(1)'><i class="fa fa-chevron-right"></i></a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
||||
@@ -30,6 +30,8 @@ export class TimePickerCtrl {
|
||||
$scope.ctrl = this;
|
||||
|
||||
$rootScope.onAppEvent('zoom-out', () => this.zoom(2), $scope);
|
||||
$rootScope.onAppEvent('shift-time-forward', () => this.move(1), $scope);
|
||||
$rootScope.onAppEvent('shift-time-backward', () => this.move(-1), $scope);
|
||||
$rootScope.onAppEvent('refresh', () => this.init(), $scope);
|
||||
$rootScope.onAppEvent('dash-editor-hidden', () => this.isOpen = false, $scope);
|
||||
|
||||
@@ -44,7 +46,7 @@ export class TimePickerCtrl {
|
||||
var time = angular.copy(this.timeSrv.timeRange());
|
||||
var timeRaw = angular.copy(this.timeSrv.timeRange(false));
|
||||
|
||||
if (this.dashboard.timezone === 'browser') {
|
||||
if (!this.dashboard.isTimezoneUtc()) {
|
||||
time.from.local();
|
||||
time.to.local();
|
||||
if (moment.isMoment(timeRaw.from)) {
|
||||
@@ -87,6 +89,30 @@ export class TimePickerCtrl {
|
||||
this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to) });
|
||||
}
|
||||
|
||||
move(direction) {
|
||||
var range = this.timeSrv.timeRange();
|
||||
|
||||
var timespan = (range.to.valueOf() - range.from.valueOf());
|
||||
var to, from;
|
||||
if (direction === -1) {
|
||||
to = range.to.valueOf() - timespan;
|
||||
from = range.from.valueOf() - timespan;
|
||||
} else if (direction === 1) {
|
||||
to = range.to.valueOf() + timespan;
|
||||
from = range.from.valueOf() + timespan;
|
||||
if (to > Date.now() && range.to < Date.now()) {
|
||||
to = Date.now();
|
||||
from = range.from.valueOf();
|
||||
}
|
||||
} else {
|
||||
to = range.to.valueOf();
|
||||
from = range.from.valueOf();
|
||||
}
|
||||
|
||||
this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to) });
|
||||
|
||||
}
|
||||
|
||||
openDropdown() {
|
||||
this.init();
|
||||
this.isOpen = true;
|
||||
@@ -125,7 +151,7 @@ export class TimePickerCtrl {
|
||||
}
|
||||
|
||||
getAbsoluteMomentForTimezone(jsDate) {
|
||||
return this.dashboard.timezone === 'browser' ? moment(jsDate) : moment(jsDate).utc();
|
||||
return this.dashboard.isTimezoneUtc() ? moment(jsDate).utc() : moment(jsDate);
|
||||
}
|
||||
|
||||
setRelativeFilter(timespan) {
|
||||
|
||||
@@ -102,7 +102,6 @@ function(angular, _) {
|
||||
value.current = null;
|
||||
value.options = null;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
p.hasChanges = function() {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
define([
|
||||
'./org_users_ctrl',
|
||||
'./profile_ctrl',
|
||||
'./org_users_ctrl',
|
||||
'./select_org_ctrl',
|
||||
'./change_password_ctrl',
|
||||
'./newOrgCtrl',
|
||||
'./userInviteCtrl',
|
||||
'./orgApiKeysCtrl',
|
||||
'./orgDetailsCtrl',
|
||||
'./prefs_control',
|
||||
], function () {});
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<prefs-control mode="org"></prefs-control>
|
||||
|
||||
<h3 class="page-heading">Address</h3>
|
||||
|
||||
|
||||
+12
-15
@@ -6,31 +6,28 @@
|
||||
<h1>Profile</h1>
|
||||
</div>
|
||||
|
||||
<form name="userForm" class="gf-form-group">
|
||||
<h3 class="page-heading">Preferences</h3>
|
||||
<form name="ctrl.userForm" class="gf-form-group">
|
||||
<h3 class="page-heading">Information</h3>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Name</span>
|
||||
<input class="gf-form-input max-width-21" type="text" required ng-model="user.name" >
|
||||
<span class="gf-form-label width-9">Name</span>
|
||||
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.name" >
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Email</span>
|
||||
<input class="gf-form-input max-width-21" type="email" required ng-model="user.email">
|
||||
<span class="gf-form-label width-9">Email</span>
|
||||
<input class="gf-form-input max-width-21" type="email" required ng-model="ctrl.user.email">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Username</span>
|
||||
<input class="gf-form-input max-width-21" type="text" required ng-model="user.login">
|
||||
<span class="gf-form-label width-9">Username</span>
|
||||
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.login">
|
||||
</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">
|
||||
<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>
|
||||
</form>
|
||||
|
||||
<prefs-control mode="user"></prefs-control>
|
||||
|
||||
<h3 class="page-heading">Password</h3>
|
||||
<div class="gf-form-group">
|
||||
<a href="profile/password" class="btn btn-inverse">Change Password</a>
|
||||
@@ -47,7 +44,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="org in orgs">
|
||||
<tr ng-repeat="org in ctrl.orgs">
|
||||
<td>{{org.name}}</td>
|
||||
<td>{{org.role}}</td>
|
||||
<td class="text-right">
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -65,7 +65,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
var data = this.panel.snapshotData;
|
||||
// backward compatability
|
||||
if (!_.isArray(data)) {
|
||||
data = data;
|
||||
data = data.data;
|
||||
}
|
||||
|
||||
this.events.emit('data-snapshot-load', data);
|
||||
@@ -82,6 +82,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
this.loading = true;
|
||||
|
||||
// load datasource service
|
||||
this.setTimeQueryStart();
|
||||
this.datasourceSrv.get(this.panel.datasource)
|
||||
.then(this.issueQueries.bind(this))
|
||||
.then(this.handleQueryResult.bind(this))
|
||||
@@ -165,6 +166,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
|
||||
issueQueries(datasource) {
|
||||
this.updateTimeRange();
|
||||
this.datasource = datasource;
|
||||
|
||||
if (!this.panel.targets || this.panel.targets.length === 0) {
|
||||
return this.$q.when([]);
|
||||
@@ -182,7 +184,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
cacheTimeout: this.panel.cacheTimeout
|
||||
};
|
||||
|
||||
this.setTimeQueryStart();
|
||||
return datasource.query(metricsQuery);
|
||||
}
|
||||
|
||||
@@ -250,9 +251,12 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
|
||||
addDataQuery(datasource) {
|
||||
var target = {
|
||||
datasource: datasource ? datasource.name : undefined
|
||||
};
|
||||
var target: any = {};
|
||||
|
||||
if (datasource) {
|
||||
target.datasource = datasource.name;
|
||||
}
|
||||
|
||||
this.panel.targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export class PanelCtrl {
|
||||
|
||||
$scope.$on("refresh", () => this.refresh());
|
||||
$scope.$on("render", () => this.render());
|
||||
$scope.$on("$destroy", () => this.events.emit('panel-teardown'));
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<gf-form-switch class="gf-form max-width-30"
|
||||
label="Hide time override info" label-class="width-12"
|
||||
checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.render()">
|
||||
checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.refresh()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,8 @@ var defaults = {
|
||||
jsonData: {}
|
||||
};
|
||||
|
||||
var datasourceCreated = false;
|
||||
|
||||
export class DataSourceEditCtrl {
|
||||
isNew: boolean;
|
||||
datasources: any[];
|
||||
@@ -66,6 +68,11 @@ export class DataSourceEditCtrl {
|
||||
this.backendSrv.get('/api/datasources/' + id).then(ds => {
|
||||
this.isNew = false;
|
||||
this.current = ds;
|
||||
|
||||
if (datasourceCreated) {
|
||||
datasourceCreated = false;
|
||||
this.testDatasource();
|
||||
}
|
||||
return this.typeChanged();
|
||||
});
|
||||
}
|
||||
@@ -123,14 +130,14 @@ export class DataSourceEditCtrl {
|
||||
if (this.current.id) {
|
||||
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
|
||||
this.updateFrontendSettings().then(() => {
|
||||
if (test) {
|
||||
this.testDatasource();
|
||||
}
|
||||
this.testDatasource();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return this.backendSrv.post('/api/datasources', this.current).then(result => {
|
||||
this.updateFrontendSettings();
|
||||
|
||||
datasourceCreated = true;
|
||||
this.$location.path('datasources/edit/' + result.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div ng-if="ctrl.tabIndex === 0" class="tab-content">
|
||||
|
||||
<form name="ctrl.editForm">
|
||||
<form name="ctrl.editForm" ng-if="ctrl.current">
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
<info-popover offset="0px -130px">
|
||||
The name is used when you select the data source in panels.
|
||||
The <code>Default</code> data source is preselected in new
|
||||
The <em>Default</em> data source is preselected in new
|
||||
panels.
|
||||
</info-popover>
|
||||
<gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch>
|
||||
@@ -55,7 +55,6 @@
|
||||
|
||||
<div ng-if="ctrl.testing" style="margin-top: 25px">
|
||||
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||
<h5 ng-show="ctrl.testing.done">Test results</h5>
|
||||
<div class="alert-{{ctrl.testing.status}} alert">
|
||||
<div class="alert-title">{{ctrl.testing.title}}</div>
|
||||
<div ng-bind='ctrl.testing.message'></div>
|
||||
@@ -64,10 +63,7 @@
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-success" ng-show="ctrl.isNew" ng-click="ctrl.saveChanges()">Add</button>
|
||||
<button type="submit" class="btn btn-success" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges()">Save</button>
|
||||
<button type="submit" class="btn btn-secondary" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges(true)">
|
||||
Test Connection
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges()">Save & Test</button>
|
||||
<button type="submit" class="btn btn-danger" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Url</span>
|
||||
<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="http://my.server.com:8080" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
|
||||
<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
|
||||
|
||||
<info-popover>
|
||||
<p>Specify a complete HTTP url (http://your_server:8080)</p>
|
||||
<p>Specify a complete HTTP url (for example http://your_server:8080)</p>
|
||||
<span ng-show="current.access === 'direct'">
|
||||
Your access method is <code>Direct</code>, this means the url
|
||||
Your access method is <em>Direct</em>, this means the url
|
||||
needs to be accessable from the browser.
|
||||
</span>
|
||||
<span ng-show="current.access === 'proxy'">
|
||||
Your access method is currently <code>Proxy</code>, this means the url
|
||||
Your access method is currently <em>Proxy</em>, this means the url
|
||||
needs to be accessable from the grafana backend.
|
||||
</span>
|
||||
</info-popover>
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">
|
||||
<a class="card-item" href="datasources/edit/{{ds.id}}/">
|
||||
<div class="card-item-header">
|
||||
<i class="icon-gf icon-gf-{{ds.type}}"></i>
|
||||
{{ds.type}}
|
||||
<div class="card-item-type">
|
||||
{{ds.type}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-item-body">
|
||||
<figure class="card-item-figure">
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="page-container" ng-init="ctrl.init()">
|
||||
<div class="page-header">
|
||||
<div class="plugin-header">
|
||||
<span ng-show="ctrl.model.info.logos.large" class="plugin-header-logo">
|
||||
<img src="{{ctrl.model.info.logos.large}}">
|
||||
<span class="plugin-header-logo">
|
||||
<img ng-src="{{ctrl.model.info.logos.large}}">
|
||||
</span>
|
||||
|
||||
<div class="plugin-header-info-block">
|
||||
@@ -55,6 +55,9 @@
|
||||
<section class="page-sidebar-section">
|
||||
<h4>Version</h4>
|
||||
<span>{{ctrl.model.info.version}}</span>
|
||||
<div ng-show="ctrl.model.hasUpdate">
|
||||
<a ng-click="ctrl.updateAvailable()" bs-tooltip="ctrl.model.latestVersion">Update Available!</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
|
||||
<h5>Includes</h4>
|
||||
|
||||
@@ -33,8 +33,13 @@
|
||||
<li class="card-item-wrapper" ng-repeat="plugin in ctrl.plugins">
|
||||
<a class="card-item" href="plugins/{{plugin.id}}/edit">
|
||||
<div class="card-item-header">
|
||||
<i class="icon-gf icon-gf-{{plugin.type}}"></i>
|
||||
{{plugin.type}}
|
||||
<div class="card-item-type">
|
||||
<i class="icon-gf icon-gf-{{plugin.type}}"></i>
|
||||
{{plugin.type}}
|
||||
</div>
|
||||
<div class="card-item-notice" ng-show="plugin.hasUpdate">
|
||||
<span bs-tooltip="plugin.latestVersion">Update available!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-item-body">
|
||||
<figure class="card-item-figure">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="modal-body">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-header-title">
|
||||
<i class="fa fa-cloud-download"></i>
|
||||
<span class="p-l-1">Update Plugin</span>
|
||||
</h2>
|
||||
|
||||
<a class="modal-header-close" ng-click="dismiss();">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<div class="gf-form-group">
|
||||
<p>Type the following on the command line to update {{plugin.name}}.</p>
|
||||
<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
|
||||
<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
|
||||
</div>
|
||||
<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,6 +19,7 @@ export class PluginEditCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope,
|
||||
private $rootScope,
|
||||
private backendSrv,
|
||||
private $routeParams,
|
||||
private $sce,
|
||||
@@ -47,6 +48,7 @@ export class PluginEditCtrl {
|
||||
});
|
||||
|
||||
if (this.model.type === 'app') {
|
||||
this.tabIndex = 1;
|
||||
this.tabs.push('Config');
|
||||
|
||||
this.hasDashboards = _.findWhere(result.includes, {type: 'dashboard'});
|
||||
@@ -73,7 +75,7 @@ export class PluginEditCtrl {
|
||||
case 'datasource': return 'icon-gf icon-gf-datasources';
|
||||
case 'panel': return 'icon-gf icon-gf-panel';
|
||||
case 'app': return 'icon-gf icon-gf-apps';
|
||||
case 'page': return 'icon-gf icon-gf-share';
|
||||
case 'page': return 'icon-gf icon-gf-endpoint-tiny';
|
||||
case 'dashboard': return 'icon-gf icon-gf-dashboard';
|
||||
}
|
||||
}
|
||||
@@ -128,6 +130,16 @@ export class PluginEditCtrl {
|
||||
this.postUpdateHook = callback;
|
||||
}
|
||||
|
||||
updateAvailable() {
|
||||
var modalScope = this.$scope.$new(true);
|
||||
modalScope.plugin = this.model;
|
||||
|
||||
this.$rootScope.appEvent('show-modal', {
|
||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
||||
scope: modalScope
|
||||
});
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.model.enabled = true;
|
||||
this.model.pinned = true;
|
||||
@@ -142,4 +154,3 @@ export class PluginEditCtrl {
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
});
|
||||
});
|
||||
@@ -272,22 +272,24 @@ function (angular, _, kbn) {
|
||||
}
|
||||
|
||||
for (i = 0; i < metricNames.length; i++) {
|
||||
var value = metricNames[i].text;
|
||||
var item = metricNames[i];
|
||||
var value = item.value || item.text;
|
||||
var text = item.text || item.value;
|
||||
|
||||
if (regex) {
|
||||
matches = regex.exec(value);
|
||||
if (!matches) { continue; }
|
||||
if (matches.length > 1) {
|
||||
value = matches[1];
|
||||
text = value;
|
||||
}
|
||||
}
|
||||
|
||||
options[value] = value;
|
||||
options[value] = {text: text, value: value};
|
||||
}
|
||||
|
||||
return _.map(_.keys(options).sort(), function(key) {
|
||||
var option = { text: key, value: key };
|
||||
return option;
|
||||
return options[key];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Vendored
+6
-2
@@ -1,5 +1,4 @@
|
||||
///<reference path="../../vendor/npm/angular2/typings/es6-promise/es6-promise.d.ts" />
|
||||
///<reference path="../../vendor/npm/angular2/typings/es6-collections/es6-collections.d.ts" />
|
||||
/// <reference path="./es6-shim/es6-shim.d.ts" />
|
||||
|
||||
declare var System: any;
|
||||
|
||||
@@ -48,3 +47,8 @@ declare module 'tether-drop' {
|
||||
var config: any;
|
||||
export default config;
|
||||
}
|
||||
|
||||
declare module 'eventemitter3' {
|
||||
var config: any;
|
||||
export default config;
|
||||
}
|
||||
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
// Type definitions for es6-promise
|
||||
// Project: https://github.com/jakearchibald/ES6-Promise
|
||||
// Definitions by: François de Campredon <https://github.com/fdecampredon/>, vvakame <https://github.com/vvakame>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface Thenable<R> {
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
|
||||
}
|
||||
|
||||
declare class Promise<R> implements Thenable<R> {
|
||||
/**
|
||||
* If you call resolve in the body of the callback passed to the constructor,
|
||||
* your promise is fulfilled with result object passed to resolve.
|
||||
* If you call reject your promise is rejected with the object passed to resolve.
|
||||
* For consistency and debugging (eg stack traces), obj should be an instanceof Error.
|
||||
* Any errors thrown in the constructor callback will be implicitly passed to reject().
|
||||
*/
|
||||
constructor(callback: (resolve : (value?: R | Thenable<R>) => void, reject: (error?: any) => void) => void);
|
||||
|
||||
/**
|
||||
* onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects.
|
||||
* Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called.
|
||||
* Both callbacks have a single parameter , the fulfillment value or rejection reason.
|
||||
* "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve.
|
||||
* If an error is thrown in the callback, the returned promise rejects with that error.
|
||||
*
|
||||
* @param onFulfilled called when/if "promise" resolves
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Promise<U>;
|
||||
|
||||
/**
|
||||
* Sugar for promise.then(undefined, onRejected)
|
||||
*
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
}
|
||||
|
||||
declare module Promise {
|
||||
/**
|
||||
* Make a new promise from the thenable.
|
||||
* A thenable is promise-like in as far as it has a "then" method.
|
||||
*/
|
||||
function resolve<R>(value?: R | Thenable<R>): Promise<R>;
|
||||
|
||||
/**
|
||||
* Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
|
||||
*/
|
||||
function reject(error: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects.
|
||||
* the array passed to all can be a mixture of promise-like objects and other objects.
|
||||
* The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
|
||||
*/
|
||||
function all<R>(promises: (R | Thenable<R>)[]): Promise<R[]>;
|
||||
|
||||
/**
|
||||
* Make a Promise that fulfills when any item fulfills, and rejects if any item rejects.
|
||||
*/
|
||||
function race<R>(promises: (R | Thenable<R>)[]): Promise<R>;
|
||||
}
|
||||
|
||||
declare module 'es6-promise' {
|
||||
var foo: typeof Promise; // Temp variable to reference Promise in local context
|
||||
module rsvp {
|
||||
export var Promise: typeof foo;
|
||||
}
|
||||
export = rsvp;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user