Compare commits
285 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6bccc5eff | |||
| 1b262d33f3 | |||
| f6d538bb98 | |||
| cbefd8f8ae | |||
| 736ee3ffff | |||
| 36c583dddf | |||
| 70d03b7a1c | |||
| 0d85254a19 | |||
| e3376136ca | |||
| 6c340dcb65 | |||
| 6b467d5b3e | |||
| 5372bcc7a2 | |||
| 0c9a19e38c | |||
| 8b4c7c94b8 | |||
| c4440eab4c | |||
| 7349427189 | |||
| 89ea25ca08 | |||
| 5d652dcaef | |||
| d69788c9df | |||
| 69c2fafa7a | |||
| 620e3a602d | |||
| ad8dbbb559 | |||
| 5a241a8290 | |||
| 4a332065d0 | |||
| 0e4b70a4a8 | |||
| f1d4fd3e67 | |||
| 7b560f9080 | |||
| 0ebd921bbb | |||
| 206cb51219 | |||
| 5e6485c210 | |||
| 65b4945548 | |||
| aa98ada141 | |||
| 26eeb6578d | |||
| 8bb07124f7 | |||
| bce5c447b3 | |||
| 34cb17546d | |||
| 7c06604f16 | |||
| 0762c38d35 | |||
| baac7809c5 | |||
| 79ed99a940 | |||
| bca0894f73 | |||
| 9316bcf307 | |||
| 60855643c4 | |||
| 650bb4c719 | |||
| 2244224b12 | |||
| bbf4d00319 | |||
| 6b813b4e2a | |||
| 529cb43397 | |||
| 6bc898b215 | |||
| 1a433de0b4 | |||
| 4d802df040 | |||
| 04a79175bf | |||
| 97656d65ec | |||
| ba28d2e13f | |||
| 711992c8e2 | |||
| 31624b410a | |||
| 00827ce921 | |||
| 21d2a51cd3 | |||
| c0384a04f8 | |||
| ee0c4cd194 | |||
| 07b13e24fa | |||
| 0855f51436 | |||
| 70acfb2cfd | |||
| 29a2b2032a | |||
| af2efbe480 | |||
| 25b0773a94 | |||
| 349f6b51a5 | |||
| 9917de09af | |||
| 884eef32c9 | |||
| 16dbf3166d | |||
| be19f8888e | |||
| 3bbc86bcf2 | |||
| 8b656f15b9 | |||
| 453f8b312a | |||
| 723bfa70db | |||
| 45dd9c5795 | |||
| fa479afdaa | |||
| 1dfeb192a3 | |||
| 1f9c2add7c | |||
| 24fbcd8dea | |||
| db15bf23b8 | |||
| de52ca4785 | |||
| 2748a3fbd8 | |||
| dfe36fb702 | |||
| 297c829bdc | |||
| bd933badb1 | |||
| 0fb4141ce4 | |||
| 51de894692 | |||
| 7079b5d770 | |||
| 41e5812a22 | |||
| 8d5f28b33a | |||
| 3925bcb94c | |||
| 8d1ac8c7f5 | |||
| 9f4ad73698 | |||
| 0108b5eb88 | |||
| 0c6841bdc7 | |||
| c455e501ac | |||
| 44c1d8c1e5 | |||
| ff22f43002 | |||
| 4e8396dbce | |||
| d38d4efc18 | |||
| a8c68e33db | |||
| 903d1b7797 | |||
| 5abaf26b5f | |||
| 485a377800 | |||
| bd21a08bc9 | |||
| 99ae38a232 | |||
| c148e54bbd | |||
| 267ab822c2 | |||
| 39cdaf5175 | |||
| 80818f80a9 | |||
| fde6eee4f4 | |||
| 14ec69a160 | |||
| 8ebe9d1e16 | |||
| 7e66d0bcc1 | |||
| 4515e66783 | |||
| 1069f485da | |||
| 4c4835e869 | |||
| a1faaa133c | |||
| 805fd18b7b | |||
| 33f7b8479e | |||
| 68bd82f1b6 | |||
| 13471ae2c4 | |||
| 822bf13b7a | |||
| 1cce5fdfe1 | |||
| 6b2fedb1c2 | |||
| e307ada374 | |||
| 4f2628c221 | |||
| 0f16066915 | |||
| 151d8db98d | |||
| 614b26fff6 | |||
| 49b2743a37 | |||
| 8e37de19c9 | |||
| 38bda92bdc | |||
| 83ba6638a1 | |||
| 4832ecf3e7 | |||
| 2e8ed97355 | |||
| d98839fc19 | |||
| b6de656cf1 | |||
| 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 | |||
| 6bb001e416 | |||
| 0a13b7c559 | |||
| 50d773988b | |||
| d2239427f2 | |||
| 24a7d4f816 | |||
| a4f9621473 | |||
| bd6a68bfe5 | |||
| a5a244767a | |||
| 55189200a4 |
+70
-3
@@ -1,4 +1,72 @@
|
||||
# 3.0.0-beta2 (unreleased)
|
||||
# 3.0.0-beta7 (unreleased)
|
||||
|
||||
### Bug fixes
|
||||
* **Dashboard title**: Fixed max dashboard title width (media query) for large screens, fixes [#4859](https://github.com/grafana/grafana/issues/4859)
|
||||
* **Annotations**: Fixed issue with entering annotation edit view, fixes [#4857](https://github.com/grafana/grafana/issues/4857)
|
||||
* **Remove query**: Fixed issue with removing query for data sources without collapsable query editors, fixes [#4856](https://github.com/grafana/grafana/issues/4856)
|
||||
* **Graphite PNG*: Fixed issue graphite png rendering option, fixes [#4864](https://github.com/grafana/grafana/issues/4864)
|
||||
* **InfluxDB**: Fixed issue missing plus group by iconn, fixes [#4862](https://github.com/grafana/grafana/issues/4862)
|
||||
|
||||
### Enhancements
|
||||
* **InfluxDB**: Added new functions moving_average and difference to query editor, closes [#4698](https://github.com/grafana/grafana/issues/4698)
|
||||
|
||||
# 3.0.0-beta6 (2016-04-29)
|
||||
|
||||
### Enhancements
|
||||
* **Singlestat**: Support for gauges in singlestat panel. closes [#3688](https://github.com/grafana/grafana/pull/3688)
|
||||
* **Templating**: Support for data source as variable, closes [#816](https://github.com/grafana/grafana/pull/816)
|
||||
|
||||
### Bug fixes
|
||||
* **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726)
|
||||
* **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755)
|
||||
* **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736)
|
||||
* **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768)
|
||||
* **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760)
|
||||
* **Table panel**: Fixed issue table panel formating string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791)
|
||||
* **grafana-cli**: Improve error message when failing to install plugins due to corrupt response, fixes [#4651](https://github.com/grafana/grafana/issues/4651)
|
||||
* **Singlestat**: Fixes prefix an postfix for gauges, fixes [#4812](https://github.com/grafana/grafana/issues/4812)
|
||||
* **Singlestat**: Fixes auto-refresh on change for some options, fixes [#4809](https://github.com/grafana/grafana/issues/4809)
|
||||
|
||||
### Breaking changes
|
||||
**Data Source Query Editors**: Issue [#3900](https://github.com/grafana/grafana/issues/3900)
|
||||
|
||||
Query editors have been updated to use the new form styles. External data source plugins needs to be
|
||||
updated to work. Sorry to introduce breaking change this late in beta phase. We wanted to get this change
|
||||
in before 3.0 stable is released so we don't have to break data sources in next release (3.1). If you are
|
||||
a data source plugin author and want help for how the new form styles work please ask for help in
|
||||
slack channel (link to slack channel in readme).
|
||||
|
||||
# 3.0.0-beta5 (2016-04-15)
|
||||
|
||||
### Bug fixes
|
||||
* **grafana-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)
|
||||
@@ -9,9 +77,8 @@
|
||||
* **Dashboard**: Fixed dashboard panel layout for mobile devices, fixes [#4529](https://github.com/grafana/grafana/issues/4529)
|
||||
* **Table Panel**: Fixed issue with table panel sort, fixes [#4532](https://github.com/grafana/grafana/issues/4532)
|
||||
* **Page Load Crash**: A Datasource with null jsonData would make Grafana fail to load page, fixes [#4536](https://github.com/grafana/grafana/issues/4536)
|
||||
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4540](https://github.com/grafana/grafana/issues/4540)
|
||||
* **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)
|
||||
* **Password**: Password reset link/page did not work, fixes [#4542](https://github.com/grafana/grafana/issues/4542)
|
||||
|
||||
# 3.0.0-beta1 (2016-03-31)
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
all: deps build
|
||||
|
||||
deps:
|
||||
go run build.go setup
|
||||
godep restore
|
||||
npm install
|
||||
|
||||
build:
|
||||
go run build.go build
|
||||
npm run build
|
||||
|
||||
test:
|
||||
godep go test -v ./pkg/...
|
||||
npm test
|
||||
|
||||
run:
|
||||
./bin/grafana-server
|
||||
@@ -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
|
||||
|
||||
@@ -100,8 +103,7 @@ npm (v2.5.0) and grunt (v0.4.5). Run the following:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm install -g grunt-cli
|
||||
grunt
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Recompile backend on source change
|
||||
@@ -142,4 +144,3 @@ please [sign the CLA](http://docs.grafana.org/project/cla/)
|
||||
|
||||
Grafana is distributed under Apache 2.0 License.
|
||||
Work in progress Grafana 2.0 (with included Grafana backend)
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ func ChangeWorkingDir(dir string) {
|
||||
}
|
||||
|
||||
func grunt(params ...string) {
|
||||
runPrint("./node_modules/grunt-cli/bin/grunt", params...)
|
||||
runPrint("./node_modules/.bin/grunt", params...)
|
||||
}
|
||||
|
||||
func setup() {
|
||||
|
||||
+2
-2
@@ -25,12 +25,12 @@ test:
|
||||
# Go test
|
||||
- godep go test -v ./pkg/...
|
||||
# js tests
|
||||
- ./node_modules/grunt-cli/bin/grunt test
|
||||
- npm test
|
||||
- npm run coveralls
|
||||
|
||||
deployment:
|
||||
master:
|
||||
branch: master
|
||||
owner: grafana
|
||||
commands:
|
||||
commands:
|
||||
- ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
influxdb:
|
||||
image: tutum/influxdb:latest
|
||||
image: tutum/influxdb:0.12
|
||||
ports:
|
||||
- "2004:2004"
|
||||
- "8083:8083"
|
||||
|
||||
@@ -191,7 +191,7 @@ Will return the home dashboard.
|
||||
|
||||
`GET /api/dashboards/tags`
|
||||
|
||||
Get all tabs of dashboards
|
||||
Get all tags of dashboards
|
||||
|
||||
**Example Request**:
|
||||
|
||||
|
||||
@@ -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/*`
|
||||
|
||||
@@ -11,7 +11,7 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
|
||||
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb)
|
||||
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta61461918338_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta61461918338_amd64.deb)
|
||||
|
||||
## Install Stable
|
||||
|
||||
@@ -21,9 +21,9 @@ Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](h
|
||||
|
||||
## Install 3.0 Beta
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta61461918338_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_3.0.0-beta11459429091_amd64.deb
|
||||
$ sudo dpkg -i grafana_3.0.0-beta61461918338_amd64.deb
|
||||
|
||||
## APT Repository
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
|
||||
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta11459429091.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm)
|
||||
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta61461918338.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta61461918338§.x86_64.rpm)
|
||||
|
||||
## Install Stable Release from package file
|
||||
|
||||
@@ -34,18 +34,18 @@ Or install manually using `rpm`.
|
||||
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta61461918338.x86_64.rpm
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||
$ sudo rpm -Uvh grafana-3.0.0-beta61461918338.x86_64.rpm
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-3.0.0-beta11459429091.x86_64.rpm
|
||||
$ sudo rpm -i --nodeps grafana-3.0.0-beta61461918338.x86_64.rpm
|
||||
|
||||
|
||||
## Install via YUM Repository
|
||||
|
||||
@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
||||
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,13 +9,13 @@ 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
|
||||
can grow even stronger and develop new plugins that we would never think about.
|
||||
|
||||
You can discover available plugins on [Grafana.net](http://grafana.net)
|
||||
You can discover available plugins on [Grafana.net](https://grafana.net)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
-2
@@ -24,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-beta5"
|
||||
}
|
||||
|
||||
+5
-4
@@ -4,13 +4,12 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "3.0.0-beta2",
|
||||
"version": "3.0.0-beta7",
|
||||
"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",
|
||||
@@ -54,8 +53,8 @@
|
||||
"mocha": "2.3.4",
|
||||
"phantomjs-prebuilt": "^2.1.3",
|
||||
"reflect-metadata": "0.1.2",
|
||||
"rxjs": "5.0.0-beta.2",
|
||||
"sass-lint": "^1.5.0",
|
||||
"rxjs": "5.0.0-beta.4",
|
||||
"sass-lint": "^1.6.0",
|
||||
"systemjs": "0.19.24"
|
||||
},
|
||||
"engines": {
|
||||
@@ -63,11 +62,13 @@
|
||||
"npm": "2.14.x"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "grunt",
|
||||
"test": "grunt test",
|
||||
"coveralls": "grunt karma:coveralls && rm -rf ./coverage"
|
||||
},
|
||||
"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,21 +1,21 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
deb_ver=3.0.0-beta11459429091
|
||||
rpm_ver=3.0.0-beta11459429091
|
||||
deb_ver=3.0.0-beta51460725904
|
||||
rpm_ver=3.0.0-beta51460725904
|
||||
|
||||
#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
|
||||
#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
|
||||
#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
|
||||
#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/6 grafana-${rpm_ver}.x86_64.rpm
|
||||
package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
|
||||
|
||||
# package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
|
||||
|
||||
+7
-3
@@ -30,6 +30,7 @@ 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)
|
||||
@@ -190,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)
|
||||
|
||||
@@ -252,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func init() {
|
||||
"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
|
||||
"AWS/ES": {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueLength", "ReadIOPS", "WriteIOPS"},
|
||||
"AWS/Events": {"Invocations", "FailedInvocations", "TriggeredRules", "MatchedEvents", "ThrottledRules"},
|
||||
"AWS/Kinesis": {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"},
|
||||
"AWS/Kinesis": {"GetRecords.Bytes", "GetRecords.IteratorAge", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Records", "GetRecords.Success", "IncomingBytes", "IncomingRecords", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "ReadProvisionedThroughputExceeded", "WriteProvisionedThroughputExceeded", "IteratorAgeMilliseconds", "OutgoingBytes", "OutgoingRecords"},
|
||||
"AWS/Lambda": {"Invocations", "Errors", "Duration", "Throttles"},
|
||||
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
|
||||
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
||||
@@ -88,7 +88,7 @@ func init() {
|
||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||
"AWS/ES": {},
|
||||
"AWS/Events": {"RuleName"},
|
||||
"AWS/Kinesis": {"StreamName"},
|
||||
"AWS/Kinesis": {"StreamName", "ShardID"},
|
||||
"AWS/Lambda": {"FunctionName"},
|
||||
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||
|
||||
@@ -21,6 +21,10 @@ func GetSharingOptions(c *middleware.Context) {
|
||||
}
|
||||
|
||||
func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
|
||||
if cmd.Name == "" {
|
||||
cmd.Name = "Unnamed snapshot"
|
||||
}
|
||||
|
||||
if cmd.External {
|
||||
// external snapshot ref requires key and delete key
|
||||
if cmd.Key == "" || cmd.DeleteKey == "" {
|
||||
|
||||
@@ -41,7 +41,6 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
} else if ds.Type == m.DS_INFLUXDB {
|
||||
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
|
||||
reqQueryVals.Add("db", ds.Database)
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
if !ds.BasicAuth {
|
||||
req.Header.Del("Authorization")
|
||||
|
||||
+12
-7
@@ -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
|
||||
@@ -43,6 +48,6 @@ func (slice PluginList) Swap(i, j int) {
|
||||
type ImportDashboardCommand struct {
|
||||
PluginId string `json:"pluginId"`
|
||||
Path string `json:"path"`
|
||||
Reinstall bool `json:"reinstall"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Inputs []plugins.ImportDashboardInput `json:"inputs"`
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
+8
-1
@@ -103,6 +103,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if !c.HasUserRole(include.Role) {
|
||||
continue
|
||||
}
|
||||
|
||||
if include.Type == "page" && include.AddToNav {
|
||||
link := &dtos.NavLink{
|
||||
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug,
|
||||
@@ -110,6 +114,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
}
|
||||
appLink.Children = append(appLink.Children, link)
|
||||
}
|
||||
|
||||
if include.Type == "dashboard" && include.AddToNav {
|
||||
link := &dtos.NavLink{
|
||||
Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug,
|
||||
@@ -124,7 +129,9 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
|
||||
}
|
||||
|
||||
data.MainNavLinks = append(data.MainNavLinks, appLink)
|
||||
if len(appLink.Children) > 0 {
|
||||
data.MainNavLinks = append(data.MainNavLinks, appLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-9
@@ -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}
|
||||
@@ -146,11 +156,12 @@ func GetPluginReadme(c *middleware.Context) Response {
|
||||
func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
|
||||
|
||||
cmd := plugins.ImportDashboardCommand{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
PluginId: apiCmd.PluginId,
|
||||
Path: apiCmd.Path,
|
||||
Inputs: apiCmd.Inputs,
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
PluginId: apiCmd.PluginId,
|
||||
Path: apiCmd.Path,
|
||||
Inputs: apiCmd.Inputs,
|
||||
Overwrite: apiCmd.Overwrite,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
|
||||
@@ -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,11 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/fatih/color"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
)
|
||||
|
||||
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
|
||||
@@ -11,7 +13,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
|
||||
|
||||
cmd := &contextCommandLine{context}
|
||||
if err := command(cmd); err != nil {
|
||||
log.Error("\nError: ")
|
||||
log.Errorf("\n%s: ", color.RedString("Error"))
|
||||
log.Errorf("%s\n\n", err)
|
||||
|
||||
cmd.ShowHelp()
|
||||
@@ -25,27 +27,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,16 +120,22 @@ 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() {
|
||||
if r := recover(); r != nil {
|
||||
retryCount++
|
||||
if retryCount == 1 {
|
||||
log.Debug("\nFailed downloading. Will retry once.\n")
|
||||
downloadFile(pluginName, filePath, url)
|
||||
if retryCount < 3 {
|
||||
fmt.Println("Failed downloading. Will retry once.")
|
||||
err = downloadFile(pluginName, filePath, url)
|
||||
} else {
|
||||
panic(r)
|
||||
failure := fmt.Sprintf("%v", r)
|
||||
if failure == "runtime error: makeslice: len out of range" {
|
||||
err = fmt.Errorf("Corrupt http response from source. Please try again.\n")
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -153,26 +159,30 @@ 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 {
|
||||
log.Errorf("%v", err)
|
||||
log.Errorf("Failed to extract file: %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
io.Copy(dst, src)
|
||||
dst.Close()
|
||||
src.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PermissionsError(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "permission denied")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package commands
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
"fmt"
|
||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
)
|
||||
@@ -15,22 +15,17 @@ func removeCommand(c CommandLine) error {
|
||||
pluginPath := c.GlobalString("pluginsDir")
|
||||
localPlugins := getPluginss(pluginPath)
|
||||
|
||||
log.Info("remove!\n")
|
||||
|
||||
plugin := c.Args().First()
|
||||
log.Info("plugin: " + plugin + "\n")
|
||||
if plugin == "" {
|
||||
return errors.New("Missing plugin parameter")
|
||||
}
|
||||
|
||||
log.Infof("plugins : \n%v\n", localPlugins)
|
||||
|
||||
for _, p := range localPlugins {
|
||||
if p.Id == c.Args().First() {
|
||||
log.Infof("removing plugin %s", p.Id)
|
||||
removePlugin(pluginPath, p.Id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("Could not find plugin named %s", c.Args().First())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,12 +13,32 @@ import (
|
||||
var version = "master"
|
||||
|
||||
func getGrafanaPluginDir() string {
|
||||
os := runtime.GOOS
|
||||
if os == "windows" {
|
||||
return "C:\\opt\\grafana\\plugins"
|
||||
} else {
|
||||
return "/var/lib/grafana/plugins"
|
||||
currentOS := runtime.GOOS
|
||||
defaultNix := "/var/lib/grafana/plugins"
|
||||
|
||||
if currentOS == "windows" {
|
||||
return "../data/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 ../conf/defaults.ini exists, grafana is not installed as package
|
||||
// that its in development environment.
|
||||
_, err := os.Stat("../conf/defaults.ini")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -45,7 +45,7 @@ type DashboardSnapshotDTO struct {
|
||||
|
||||
type CreateDashboardSnapshotCommand struct {
|
||||
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Name string `json:"name"`
|
||||
Expires int64 `json:"expires"`
|
||||
|
||||
// these are passed when storing an external snapshot ref
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -37,6 +39,26 @@ func (r RoleType) Includes(other RoleType) bool {
|
||||
return r == other
|
||||
}
|
||||
|
||||
func (r *RoleType) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = RoleType(str)
|
||||
|
||||
if (*r).IsValid() == false {
|
||||
if (*r) != "" {
|
||||
return errors.New(fmt.Sprintf("JSON validation error: invalid role value: %s", *r))
|
||||
}
|
||||
|
||||
*r = ROLE_VIEWER
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type OrgUser struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
)
|
||||
|
||||
type ImportDashboardCommand struct {
|
||||
Path string `json:"string"`
|
||||
Inputs []ImportDashboardInput `json:"inputs"`
|
||||
Path string
|
||||
Inputs []ImportDashboardInput
|
||||
Overwrite bool
|
||||
|
||||
OrgId int64 `json:"-"`
|
||||
UserId int64 `json:"-"`
|
||||
PluginId string `json:"-"`
|
||||
OrgId int64
|
||||
UserId int64
|
||||
PluginId string
|
||||
Result *PluginDashboardInfoDTO
|
||||
}
|
||||
|
||||
@@ -67,6 +68,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
|
||||
Dashboard: generatedDash,
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
Overwrite: cmd.Overwrite,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&saveCmd); err != nil {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -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:"-"`
|
||||
@@ -65,6 +69,12 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error {
|
||||
pb.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
for _, include := range pb.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = m.RoleType(m.ROLE_VIEWER)
|
||||
}
|
||||
}
|
||||
|
||||
pb.PluginDir = pluginDir
|
||||
Plugins[pb.Id] = pb
|
||||
return nil
|
||||
@@ -76,14 +86,14 @@ type PluginDependencies struct {
|
||||
}
|
||||
|
||||
type PluginInclude struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
Component string `json:"component"`
|
||||
Role models.RoleType `json:"role"`
|
||||
AddToNav bool `json:"addToNav"`
|
||||
DefaultNav bool `json:"defaultNav"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
Component string `json:"component"`
|
||||
Role m.RoleType `json:"role"`
|
||||
AddToNav bool `json:"addToNav"`
|
||||
DefaultNav bool `json:"defaultNav"`
|
||||
Slug string `json:"slug"`
|
||||
|
||||
Id string `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
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
|
||||
has, err := x.Get(&dashboard)
|
||||
has, err := sess.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has == false {
|
||||
|
||||
@@ -10,12 +10,12 @@ func addPreferencesMigrations(mg *Migrator) {
|
||||
Name: "preferences",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_Int, Nullable: true},
|
||||
{Name: "user_id", Type: DB_NVarchar, Length: 255, Nullable: true},
|
||||
{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: true},
|
||||
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: true},
|
||||
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: true},
|
||||
{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},
|
||||
},
|
||||
@@ -25,6 +25,8 @@ func addPreferencesMigrations(mg *Migrator) {
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("drop preferences table v3", NewDropTableMigration("preferences"))
|
||||
|
||||
// create table
|
||||
mg.AddMigration("create preferences table v2", NewAddTableMigration(preferencesV2))
|
||||
mg.AddMigration("create preferences table v3", NewAddTableMigration(preferencesV2))
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
|
||||
for key, data := range cmd.SecureJsonData {
|
||||
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
|
||||
}
|
||||
pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
|
||||
pluginSetting.Updated = time.Now()
|
||||
pluginSetting.Enabled = cmd.Enabled
|
||||
pluginSetting.JsonData = cmd.JsonData
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -7,6 +7,9 @@ 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>
|
||||
<info-popover mode="right-absolute">
|
||||
Not finding dashboard you want? Star it first, then it should appear in this select box.
|
||||
</info-popover>
|
||||
`;
|
||||
|
||||
export class DashboardSelectorCtrl {
|
||||
|
||||
@@ -8,21 +8,24 @@ import Drop from 'tether-drop';
|
||||
export function infoPopover() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<i class="fa fa-info-circle"></i>',
|
||||
transclude: true,
|
||||
link: function(scope, elem, attrs, ctrl, transclude) {
|
||||
var inputElem = elem.prev();
|
||||
if (inputElem.length === 0) {
|
||||
console.log('Failed to find input element for popover');
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = attrs.offset || '0 -10px';
|
||||
var position = attrs.position || 'right middle';
|
||||
var classes = 'drop-help drop-hide-out-of-bounds';
|
||||
var openOn = 'hover';
|
||||
|
||||
elem.addClass('gf-form-help-icon');
|
||||
|
||||
if (attrs.wide) {
|
||||
classes += ' drop-wide';
|
||||
}
|
||||
|
||||
if (attrs.mode) {
|
||||
elem.addClass('gf-form-help-icon--' + attrs.mode);
|
||||
}
|
||||
|
||||
transclude(function(clone, newScope) {
|
||||
var content = document.createElement("div");
|
||||
_.each(clone, (node) => {
|
||||
@@ -30,11 +33,12 @@ export function infoPopover() {
|
||||
});
|
||||
|
||||
var drop = new Drop({
|
||||
target: inputElem[0],
|
||||
target: elem[0],
|
||||
content: content,
|
||||
position: position,
|
||||
classes: classes,
|
||||
openOn: 'click',
|
||||
openOn: openOn,
|
||||
hoverOpenDelay: 400,
|
||||
tetherOptions: {
|
||||
offset: offset
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -7,7 +7,12 @@ import coreModule from 'app/core/core_module';
|
||||
import Drop from 'tether-drop';
|
||||
|
||||
var template = `
|
||||
<label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">{{ctrl.label}}</label>
|
||||
<label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">
|
||||
{{ctrl.label}}
|
||||
<info-popover mode="right-normal" ng-if="ctrl.tooltip">
|
||||
{{ctrl.tooltip}}
|
||||
</info-popover>
|
||||
</label>
|
||||
<div class="gf-form-switch {{ctrl.switchClass}}" ng-if="ctrl.show">
|
||||
<input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()">
|
||||
<label for="check-{{ctrl.id}}" data-on="Yes" data-off="No"></label>
|
||||
@@ -21,17 +26,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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,22 +54,6 @@ export function switchDirective() {
|
||||
onChange: "&",
|
||||
},
|
||||
template: template,
|
||||
link: (scope, elem) => {
|
||||
if (scope.ctrl.tooltip) {
|
||||
var drop = new Drop({
|
||||
target: elem[0],
|
||||
content: scope.ctrl.tooltip,
|
||||
position: "right middle",
|
||||
classes: 'drop-help',
|
||||
openOn: 'hover',
|
||||
hoverOpenDelay: 400,
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
drop.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -9,10 +9,10 @@ function (_, $, coreModule) {
|
||||
coreModule.default.directive('dropdownTypeahead', function($compile) {
|
||||
|
||||
var inputTemplate = '<input type="text"'+
|
||||
' class="tight-form-input input-medium tight-form-input"' +
|
||||
' class="gf-form-input input-medium tight-form-input"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="tight-form-item tight-form-func dropdown-toggle"' +
|
||||
var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
|
||||
' data-placement="top"><i class="fa fa-plus"></i></a>';
|
||||
|
||||
|
||||
@@ -8,10 +8,13 @@ function (_, $, coreModule) {
|
||||
|
||||
coreModule.default.directive('metricSegment', function($compile, $sce) {
|
||||
var inputTemplate = '<input type="text" data-provide="typeahead" ' +
|
||||
' class="tight-form-clear-input input-medium"' +
|
||||
' class="gf-form-input input-medium"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
|
||||
var linkTemplate = '<a class="gf-form-label" ng-class="segment.cssClass" ' +
|
||||
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
|
||||
var selectTemplate = '<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
|
||||
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
|
||||
return {
|
||||
@@ -20,9 +23,9 @@ function (_, $, coreModule) {
|
||||
getOptions: "&",
|
||||
onChange: "&",
|
||||
},
|
||||
link: function($scope, elem) {
|
||||
link: function($scope, elem, attrs) {
|
||||
var $input = $(inputTemplate);
|
||||
var $button = $(buttonTemplate);
|
||||
var $button = $(attrs.styleMode === 'select' ? selectTemplate : linkTemplate);
|
||||
var segment = $scope.segment;
|
||||
var options = null;
|
||||
var cancelBlur = null;
|
||||
|
||||
@@ -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,
|
||||
@@ -206,9 +206,15 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
});
|
||||
|
||||
$compile(child)(scope);
|
||||
|
||||
elem.empty();
|
||||
elem.append(child);
|
||||
|
||||
// let a binding digest cycle complete before adding to dom
|
||||
setTimeout(function() {
|
||||
elem.append(child);
|
||||
scope.$apply(function() {
|
||||
scope.$broadcast('refresh');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerPluginComponent(scope, elem, attrs, componentInfo) {
|
||||
|
||||
@@ -25,7 +25,6 @@ export class ContextSrv {
|
||||
isGrafanaAdmin: any;
|
||||
isEditor: any;
|
||||
sidemenu: any;
|
||||
lightTheme: any;
|
||||
|
||||
constructor() {
|
||||
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
|
||||
@@ -41,7 +40,6 @@ export class ContextSrv {
|
||||
}
|
||||
|
||||
this.version = config.buildInfo.version;
|
||||
this.lightTheme = false;
|
||||
this.user = new User();
|
||||
this.isSignedIn = this.user.isSignedIn;
|
||||
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
|
||||
|
||||
@@ -7,36 +7,11 @@ define([
|
||||
function (angular, _, coreModule, config) {
|
||||
'use strict';
|
||||
|
||||
coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope) {
|
||||
coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope, templateSrv) {
|
||||
var self = this;
|
||||
|
||||
this.init = function() {
|
||||
this.datasources = {};
|
||||
this.metricSources = [];
|
||||
this.annotationSources = [];
|
||||
|
||||
_.each(config.datasources, function(value, key) {
|
||||
if (value.meta && value.meta.metrics) {
|
||||
self.metricSources.push({
|
||||
value: key === config.defaultDatasource ? null : key,
|
||||
name: key,
|
||||
meta: value.meta,
|
||||
});
|
||||
}
|
||||
if (value.meta && value.meta.annotations) {
|
||||
self.annotationSources.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
this.metricSources.sort(function(a, b) {
|
||||
if (a.meta.builtIn || a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
this.get = function(name) {
|
||||
@@ -44,6 +19,8 @@ function (angular, _, coreModule, config) {
|
||||
return this.get(config.defaultDatasource);
|
||||
}
|
||||
|
||||
name = templateSrv.replace(name);
|
||||
|
||||
if (this.datasources[name]) {
|
||||
return $q.when(this.datasources[name]);
|
||||
}
|
||||
@@ -89,11 +66,61 @@ function (angular, _, coreModule, config) {
|
||||
};
|
||||
|
||||
this.getAnnotationSources = function() {
|
||||
return this.annotationSources;
|
||||
return _.reduce(config.datasources, function(memo, value) {
|
||||
|
||||
if (value.meta && value.meta.annotations) {
|
||||
memo.push(value);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, []);
|
||||
};
|
||||
|
||||
this.getMetricSources = function() {
|
||||
return this.metricSources;
|
||||
this.getMetricSources = function(options) {
|
||||
var metricSources = [];
|
||||
|
||||
_.each(config.datasources, function(value, key) {
|
||||
if (value.meta && value.meta.metrics) {
|
||||
metricSources.push({
|
||||
value: key === config.defaultDatasource ? null : key,
|
||||
name: key,
|
||||
meta: value.meta,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!options || !options.skipVariables) {
|
||||
// look for data source variables
|
||||
for (var i = 0; i < templateSrv.variables.length; i++) {
|
||||
var variable = templateSrv.variables[i];
|
||||
if (variable.type !== 'datasource') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var first = variable.current.value;
|
||||
var ds = config.datasources[first];
|
||||
|
||||
if (ds) {
|
||||
metricSources.push({
|
||||
name: '$' + variable.name,
|
||||
value: '$' + variable.name,
|
||||
meta: ds.meta,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metricSources.sort(function(a, b) {
|
||||
if (a.meta.builtIn || a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return metricSources;
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ function (angular, _, coreModule) {
|
||||
|
||||
if (_.isString(options)) {
|
||||
this.value = options;
|
||||
this.html = $sce.trustAsHtml(this.value);
|
||||
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ export default class TableModel {
|
||||
if (options.desc) {
|
||||
this.rows.reverse();
|
||||
this.columns[options.col].desc = true;
|
||||
} else {
|
||||
this.columns[options.col].desc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
scope.$on('$destroy', () => {
|
||||
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>
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -184,6 +184,7 @@ function (angular, $, _, moment) {
|
||||
p.formatDate = function(date, format) {
|
||||
date = moment.isMoment(date) ? date : moment(date);
|
||||
format = format || 'YYYY-MM-DD HH:mm:ss';
|
||||
this.timezone = this.getTimezone();
|
||||
|
||||
return this.timezone === 'browser' ?
|
||||
moment(date).format(format) :
|
||||
@@ -428,6 +429,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 = [
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-show="::dashboardMeta.canSave">
|
||||
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||
</li>
|
||||
<li ng-if="::showSettingsMenu" class="dropdown">
|
||||
<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Manage dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
|
||||
@@ -48,7 +48,6 @@
|
||||
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="editJson();">View JSON</a></li>
|
||||
<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
|
||||
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
|
||||
<li ng-if="dashboard.id"><a class="pointer" ng-click="saveDashboardAsHome();">Set As Home</a></li>
|
||||
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -172,7 +172,7 @@ export class DashNavCtrl {
|
||||
var clone = $scope.dashboard.getSaveModelClone();
|
||||
var blob = new Blob([angular.toJson(clone, true)], { type: "application/json;charset=utf-8" });
|
||||
var wnd: any = window;
|
||||
wnd.saveAs(blob, $scope.dashboard.title + '-' + new Date().getTime());
|
||||
wnd.saveAs(blob, $scope.dashboard.title + '-' + new Date().getTime() + '.json');
|
||||
};
|
||||
|
||||
$scope.snapshot = function() {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
<input type="text" class="gf-form-input width-25" ng-model='dashboard.title'></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Tags<tip>Press enter to a add tag</tip></label>
|
||||
<label class="gf-form-label width-7">
|
||||
Tags
|
||||
<info-popover mode="right-normal">Press enter to a add tag</info-popover>
|
||||
</label>
|
||||
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
</div>
|
||||
@@ -46,19 +49,19 @@
|
||||
label="Editable"
|
||||
tooltip="Uncheck, then save and reload to disable all dashboard editing"
|
||||
checked="dashboard.editable"
|
||||
label-class="width-10">
|
||||
label-class="width-11">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Hide Controls"
|
||||
tooltip="Hide row controls. Shortcut: CTRL+H"
|
||||
checked="dashboard.hideControls"
|
||||
label-class="width-10">
|
||||
label-class="width-11">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Shared Crosshair"
|
||||
tooltip="Shared Crosshair line on all graphs. Shortcut: CTRL+O"
|
||||
checked="dashboard.sharedCrosshair"
|
||||
label-class="width-10">
|
||||
label-class="width-11">
|
||||
</gf-form-switch>
|
||||
</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 out <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);
|
||||
|
||||
@@ -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()) / 2;
|
||||
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;
|
||||
|
||||
@@ -102,7 +102,6 @@ function(angular, _) {
|
||||
value.current = null;
|
||||
value.options = null;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
p.hasChanges = function() {
|
||||
|
||||
@@ -36,7 +36,7 @@ function (angular, _, $) {
|
||||
self.update(payload);
|
||||
});
|
||||
|
||||
$scope.onAppEvent('panel-instantiated', function(evt, payload) {
|
||||
$scope.onAppEvent('panel-initialized', function(evt, payload) {
|
||||
self.registerPanel(payload.scope);
|
||||
});
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export class PrefsControlCtrl {
|
||||
}
|
||||
|
||||
var template = `
|
||||
<form name="ctrl.prefsForm" class="gf-form-group">
|
||||
<form name="ctrl.prefsForm" class="section gf-form-group">
|
||||
<h3 class="page-heading">Preferences</h3>
|
||||
|
||||
<div class="gf-form">
|
||||
@@ -61,9 +61,8 @@ var template = `
|
||||
|
||||
<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 class="gf-form-select-wrapper max-width-20 gf-form-select-wrapper--has-help-icon"
|
||||
model="ctrl.prefs.homeDashboardId">
|
||||
</dashboard-selector>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,4 +5,5 @@ define([
|
||||
'./query_ctrl',
|
||||
'./panel_editor_tab',
|
||||
'./query_editor_row',
|
||||
'./metrics_ds_selector',
|
||||
], function () {});
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
var template = `
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<i class="icon-gf icon-gf-datasource"></i>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
Panel data source
|
||||
</label>
|
||||
|
||||
<metric-segment segment="ctrl.dsSegment" style-mode="select"
|
||||
get-options="ctrl.getOptions()"
|
||||
on-change="ctrl.datasourceChanged()"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--offset-1">
|
||||
<button class="btn btn-inverse gf-form-btn" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.current.meta.mixed">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add query
|
||||
</button>
|
||||
|
||||
<div class="dropdown" ng-if="ctrl.current.meta.mixed">
|
||||
<button class="btn btn-inverse dropdown-toggle gf-form-btn" data-toggle="dropdown">
|
||||
Add Query <span class="fa fa-caret-down"></span>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
|
||||
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
export class MetricsDsSelectorCtrl {
|
||||
dsSegment: any;
|
||||
dsName: string;
|
||||
panelCtrl: any;
|
||||
datasources: any[];
|
||||
current: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private uiSegmentSrv, datasourceSrv) {
|
||||
this.datasources = datasourceSrv.getMetricSources();
|
||||
|
||||
var dsValue = this.panelCtrl.panel.datasource || null;
|
||||
|
||||
for (let ds of this.datasources) {
|
||||
if (ds.value === dsValue) {
|
||||
this.current = ds;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.current) {
|
||||
this.current = {name: dsValue + ' not found', value: null};
|
||||
}
|
||||
|
||||
this.dsSegment = uiSegmentSrv.newSegment(this.current.name);
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return Promise.resolve(this.datasources.map(value => {
|
||||
return this.uiSegmentSrv.newSegment(value.name);
|
||||
}));
|
||||
}
|
||||
|
||||
datasourceChanged() {
|
||||
var ds = _.findWhere(this.datasources, {name: this.dsSegment.value});
|
||||
if (ds) {
|
||||
this.current = ds;
|
||||
this.panelCtrl.setDatasource(ds);
|
||||
}
|
||||
}
|
||||
|
||||
addDataQuery(datasource) {
|
||||
var target: any = {isNew: true};
|
||||
|
||||
if (datasource) {
|
||||
target.datasource = datasource.name;
|
||||
}
|
||||
|
||||
this.panelCtrl.panel.targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
module.directive('metricsDsSelector', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
controller: MetricsDsSelectorCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
transclude: true,
|
||||
scope: {
|
||||
panelCtrl: "="
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -15,6 +15,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
error: boolean;
|
||||
loading: boolean;
|
||||
datasource: any;
|
||||
datasourceName: any;
|
||||
$q: any;
|
||||
$timeout: any;
|
||||
datasourceSrv: any;
|
||||
@@ -27,7 +28,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
resolution: any;
|
||||
timeInfo: any;
|
||||
skipDataOnInit: boolean;
|
||||
datasources: any[];
|
||||
dataStream: any;
|
||||
dataSubscription: any;
|
||||
|
||||
@@ -52,7 +52,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
private onInitMetricsPanelEditMode() {
|
||||
this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
|
||||
this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
|
||||
this.datasources = this.datasourceSrv.getMetricSources();
|
||||
}
|
||||
|
||||
private onMetricsPanelRefresh() {
|
||||
@@ -65,7 +64,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 +81,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))
|
||||
@@ -183,7 +183,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
cacheTimeout: this.panel.cacheTimeout
|
||||
};
|
||||
|
||||
this.setTimeQueryStart();
|
||||
return datasource.query(metricsQuery);
|
||||
}
|
||||
|
||||
@@ -246,15 +245,10 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
|
||||
this.panel.datasource = datasource.value;
|
||||
this.datasourceName = datasource.name;
|
||||
this.datasource = null;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
addDataQuery(datasource) {
|
||||
var target = {
|
||||
};
|
||||
this.panel.targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
export {MetricsPanelCtrl};
|
||||
|
||||
@@ -46,12 +46,13 @@ export class PanelCtrl {
|
||||
|
||||
$scope.$on("refresh", () => this.refresh());
|
||||
$scope.$on("render", () => this.render());
|
||||
$scope.$on("$destroy", () => this.events.emit('panel-teardown'));
|
||||
}
|
||||
|
||||
init() {
|
||||
this.publishAppEvent('panel-instantiated', {scope: this.$scope});
|
||||
this.calculatePanelHeight();
|
||||
this.refresh();
|
||||
this.publishAppEvent('panel-initialized', {scope: this.$scope});
|
||||
this.events.emit('panel-initialized');
|
||||
}
|
||||
|
||||
renderingCompleted() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,63 @@
|
||||
<div class="tight-form">
|
||||
|
||||
<div class="gf-form-query">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label gf-form-query-letter-cell">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
|
||||
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
|
||||
<i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
|
||||
<i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
|
||||
</span>
|
||||
<span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
|
||||
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
|
||||
{{ctrl.collapsedText}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
|
||||
<i class="fa fa-bars"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
|
||||
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
|
||||
</li>
|
||||
</ul>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form" ng-if="false">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li ng-show="ctrl.error" class="tight-form-item">
|
||||
<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
|
||||
@@ -13,45 +13,11 @@ export class QueryCtrl {
|
||||
|
||||
constructor(public $scope, private $injector) {
|
||||
this.panel = this.panelCtrl.panel;
|
||||
|
||||
if (!this.target.refId) {
|
||||
this.target.refId = this.getNextQueryLetter();
|
||||
}
|
||||
}
|
||||
|
||||
getNextQueryLetter() {
|
||||
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
return _.find(letters, refId => {
|
||||
return _.every(this.panel.targets, function(other) {
|
||||
return other.refId !== refId;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeQuery() {
|
||||
this.panel.targets = _.without(this.panel.targets, this.target);
|
||||
this.panelCtrl.refresh();
|
||||
};
|
||||
|
||||
duplicateQuery() {
|
||||
var clone = angular.copy(this.target);
|
||||
clone.refId = this.getNextQueryLetter();
|
||||
this.panel.targets.push(clone);
|
||||
}
|
||||
|
||||
moveQuery(direction) {
|
||||
var index = _.indexOf(this.panel.targets, this.target);
|
||||
_.move(this.panel.targets, index, index + direction);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
toggleHideQuery() {
|
||||
this.target.hide = !this.target.hide;
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,118 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
export class QueryRowCtrl {
|
||||
collapsedText: string;
|
||||
canCollapse: boolean;
|
||||
getCollapsedText: any;
|
||||
target: any;
|
||||
queryCtrl: any;
|
||||
panelCtrl: any;
|
||||
panel: any;
|
||||
collapsed: any;
|
||||
|
||||
constructor() {
|
||||
this.panelCtrl = this.queryCtrl.panelCtrl;
|
||||
this.target = this.queryCtrl.target;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
|
||||
if (!this.target.refId) {
|
||||
this.target.refId = this.getNextQueryLetter();
|
||||
}
|
||||
|
||||
this.toggleCollapse(true);
|
||||
if (this.target.isNew) {
|
||||
delete this.target.isNew;
|
||||
this.toggleCollapse(false);
|
||||
}
|
||||
}
|
||||
|
||||
toggleHideQuery() {
|
||||
this.target.hide = !this.target.hide;
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
getNextQueryLetter() {
|
||||
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
return _.find(letters, refId => {
|
||||
return _.every(this.panel.targets, function(other) {
|
||||
return other.refId !== refId;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggleCollapse(init) {
|
||||
if (!this.canCollapse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.panelCtrl.__collapsedQueryCache) {
|
||||
this.panelCtrl.__collapsedQueryCache = {};
|
||||
}
|
||||
|
||||
if (init) {
|
||||
this.collapsed = this.panelCtrl.__collapsedQueryCache[this.target.refId] !== false;
|
||||
} else {
|
||||
this.collapsed = !this.collapsed;
|
||||
this.panelCtrl.__collapsedQueryCache[this.target.refId] = this.collapsed;
|
||||
}
|
||||
|
||||
try {
|
||||
this.collapsedText = this.queryCtrl.getCollapsedText();
|
||||
} catch (e) {
|
||||
var err = e.message || e.toString();
|
||||
this.collapsedText = 'Error: ' + err;
|
||||
}
|
||||
}
|
||||
|
||||
toggleEditorMode() {
|
||||
if (this.canCollapse && this.collapsed) {
|
||||
this.collapsed = false;
|
||||
}
|
||||
|
||||
this.queryCtrl.toggleEditorMode();
|
||||
}
|
||||
|
||||
removeQuery() {
|
||||
if (this.panelCtrl.__collapsedQueryCache) {
|
||||
delete this.panelCtrl.__collapsedQueryCache[this.target.refId];
|
||||
}
|
||||
|
||||
this.panel.targets = _.without(this.panel.targets, this.target);
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
duplicateQuery() {
|
||||
var clone = angular.copy(this.target);
|
||||
clone.refId = this.getNextQueryLetter();
|
||||
this.panel.targets.push(clone);
|
||||
}
|
||||
|
||||
moveQuery(direction) {
|
||||
var index = _.indexOf(this.panel.targets, this.target);
|
||||
_.move(this.panel.targets, index, index + direction);
|
||||
}
|
||||
}
|
||||
|
||||
/** @ngInject **/
|
||||
function queryEditorRowDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: QueryRowCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: "ctrl",
|
||||
templateUrl: 'public/app/features/panel/partials/query_editor_row.html',
|
||||
transclude: true,
|
||||
scope: {ctrl: "="},
|
||||
scope: {
|
||||
queryCtrl: "=",
|
||||
canCollapse: "=",
|
||||
hasTextEditMode: "=",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -91,9 +98,7 @@ export class DataSourceEditCtrl {
|
||||
|
||||
this.datasourceSrv.get(this.current.name).then(datasource => {
|
||||
if (!datasource.testDatasource) {
|
||||
this.testing.message = 'Data source does not support test connection feature.';
|
||||
this.testing.status = 'warning';
|
||||
this.testing.title = 'Unknown';
|
||||
delete this.testing;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,7 +116,9 @@ export class DataSourceEditCtrl {
|
||||
}
|
||||
});
|
||||
}).finally(() => {
|
||||
this.testing.done = true;
|
||||
if (this.testing) {
|
||||
this.testing.done = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
</td>
|
||||
<td>
|
||||
v{{dash.revision}}
|
||||
</td>
|
||||
<td ng-if="dash.installed">
|
||||
Imported v{{dash.installedRevision}}
|
||||
<span ng-if="dash.installed">
|
||||
(Imported v{{dash.installedRevision}})
|
||||
<span>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
|
||||
Import
|
||||
</button>
|
||||
<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
|
||||
Re-Import
|
||||
Update
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
|
||||
Delete
|
||||
|
||||
@@ -43,11 +43,11 @@ export class DashImportListCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
import(dash, reinstall) {
|
||||
import(dash, overwrite) {
|
||||
var installCmd = {
|
||||
pluginId: this.plugin.id,
|
||||
path: dash.path,
|
||||
reinstall: reinstall,
|
||||
overwrite: overwrite,
|
||||
inputs: []
|
||||
};
|
||||
|
||||
|
||||
@@ -25,24 +25,24 @@
|
||||
|
||||
<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">
|
||||
<span class="gf-form-label width-7">Name</span>
|
||||
<input class="gf-form-input max-width-21" type="text" ng-model="ctrl.current.name" placeholder="My data source name" required>
|
||||
<info-popover offset="0px -135px" mode="right-absolute">
|
||||
The name is used when you select the data source in panels.
|
||||
The <em>Default</em> data source is preselected in new
|
||||
panels.
|
||||
</info-popover>
|
||||
</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
|
||||
panels.
|
||||
</info-popover>
|
||||
<gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Type</span>
|
||||
<div class="gf-form-select-wrapper max-width-21">
|
||||
<div class="gf-form-select-wrapper max-width-23">
|
||||
<select class="gf-form-input" ng-model="ctrl.current.type" ng-options="v.id as v.name for v in ctrl.types" ng-change="ctrl.typeChanged()"></select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,9 +53,8 @@
|
||||
</plugin-component>
|
||||
</rebuild-on-change>
|
||||
|
||||
<div ng-if="ctrl.testing" style="margin-top: 25px">
|
||||
<div ng-if="ctrl.testing" class="gf-form-group">
|
||||
<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>
|
||||
|
||||
@@ -3,29 +3,34 @@
|
||||
<div class="gf-form-group">
|
||||
<h3 class="page-heading">Http settings</h3>
|
||||
|
||||
<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>
|
||||
|
||||
<info-popover>
|
||||
<p>Specify a complete HTTP url (http://your_server:8080)</p>
|
||||
<span ng-show="current.access === 'direct'">
|
||||
Your access method is <code>Direct</code>, this means the url
|
||||
needs to be accessable from the browser.
|
||||
</span>
|
||||
<span ng-show="current.access === 'proxy'">
|
||||
Your access method is currently <code>Proxy</code>, this means the url
|
||||
needs to be accessable from the grafana backend.
|
||||
</span>
|
||||
</info-popover>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-7">Url</span>
|
||||
<input class="gf-form-input" 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 mode="right-absolute">
|
||||
<p>Specify a complete HTTP url (for example http://your_server:8080)</p>
|
||||
<span ng-show="current.access === 'direct'">
|
||||
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 <em>Proxy</em>, this means the url
|
||||
needs to be accessable from the grafana backend.
|
||||
</span>
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">
|
||||
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip>
|
||||
</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-7">Access</span>
|
||||
<div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24">
|
||||
<select class="gf-form-input" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
|
||||
<info-popover mode="right-absolute">
|
||||
Direct = url is used directly from browser<br>
|
||||
Proxy = Grafana backend will proxy the request
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,12 +39,12 @@
|
||||
<label class="gf-form-label width-7">Http Auth</label>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Basic Auth"
|
||||
checked="current.basicAuth" switch-class="max-width-6">
|
||||
label="Basic Auth"
|
||||
checked="current.basicAuth" switch-class="max-width-6">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="With Credentials"
|
||||
checked="current.withCredentials" switch-class="max-width-6">
|
||||
label="With Credentials"
|
||||
checked="current.withCredentials" switch-class="max-width-6">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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="https://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"><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);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user