diff --git a/CHANGELOG.md b/CHANGELOG.md
index e97ab145c61..225e1db96ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,14 +6,15 @@
* **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018)
### Breaking changes
-**Plugin API**: Both datasource and panel plugin api (and plugin.json schema) as been updated, requiring a minor update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.
-**InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds. Can easily be installed via improved plugin system, closes #3523
-**KairosDB** The data source is no longer included in default builds. Can easily be installed via improved plugin system, closes #3524
+* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring a minor update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.
+* **InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3523](https://github.com/grafana/grafana/issues/3523)
+* **KairosDB** The data source is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3524](https://github.com/grafana/grafana/issues/3524)
### Enhancements
* **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/pull/3458)
* **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/pull/3584)
* **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/pull/3635)
+* **Admin**: Admin can now have global overview of Grafana setup, closes [#3812](https://github.com/grafana/grafana/issues/3812)
### Bug fixes
* **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794)
diff --git a/circle.yml b/circle.yml
index bb22fcb0a6e..22dab8ab893 100644
--- a/circle.yml
+++ b/circle.yml
@@ -12,6 +12,8 @@ dependencies:
- mkdir -p ${GOPATH}/src/${ORG_PATH}
- ln -s ~/grafana ${GOPATH}/src/${ORG_PATH}
- go get github.com/tools/godep
+ - rm -rf node_modules
+ - npm install -g npm
- npm install
test:
@@ -25,3 +27,10 @@ test:
# js tests
- ./node_modules/grunt-cli/bin/grunt test
- npm run coveralls
+
+deployment:
+ master:
+ branch: master
+ owner: grafana
+ commands:
+ - ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}
diff --git a/docker/blocks/elastic/elasticsearch/config/.placeholder b/docker/blocks/elastic/elasticsearch/config/.placeholder
new file mode 100644
index 00000000000..9ad266259c2
--- /dev/null
+++ b/docker/blocks/elastic/elasticsearch/config/.placeholder
@@ -0,0 +1 @@
+Ensure the existence of the parent folder.
diff --git a/docker/blocks/elastic/fig b/docker/blocks/elastic/fig
new file mode 100644
index 00000000000..498402ac7b0
--- /dev/null
+++ b/docker/blocks/elastic/fig
@@ -0,0 +1,6 @@
+elasticsearch:
+ image: elasticsearch:latest
+ command: elasticsearch -Des.network.host=0.0.0.0
+ ports:
+ - "9200:9200"
+ - "9300:9300"
diff --git a/docker/blocks/graphite/Dockerfile b/docker/blocks/graphite/Dockerfile
index 0c85798dd8a..aeac91b7a58 100644
--- a/docker/blocks/graphite/Dockerfile
+++ b/docker/blocks/graphite/Dockerfile
@@ -1,68 +1,50 @@
-from ubuntu:14.10
+from ubuntu:14.04
-run apt-get -y update
+run apt-get -y update
-run apt-get -y install software-properties-common
-
-run apt-get -y install python-software-properties &&\
- add-apt-repository ppa:chris-lea/node.js &&\
- apt-get -y update
-
-run apt-get -y install python-django-tagging python-simplejson python-memcache \
- python-ldap python-cairo python-django python-twisted \
- python-pysqlite2 python-support python-pip gunicorn \
- supervisor nginx-light nodejs git wget curl
-
-# Install statsd
-run mkdir /src && git clone https://github.com/etsy/statsd.git /src/statsd
+run apt-get -y install libcairo2-dev libffi-dev pkg-config python-dev python-pip fontconfig apache2 libapache2-mod-wsgi git-core collectd memcached gcc g++ make supervisor nginx-light gunicorn
run cd /usr/local/src && git clone https://github.com/graphite-project/graphite-web.git
run cd /usr/local/src && git clone https://github.com/graphite-project/carbon.git
run cd /usr/local/src && git clone https://github.com/graphite-project/whisper.git
run cd /usr/local/src/whisper && git checkout master && python setup.py install
-run cd /usr/local/src/carbon && git checkout 0.9.x && python setup.py install
-run cd /usr/local/src/graphite-web && git checkout 0.9.x && python check-dependencies.py; python setup.py install
-
-# statsd
-add ./files/statsd_config.js /src/statsd/config.js
+run cd /usr/local/src/carbon && git checkout 0.9.x && pip install -r requirements.txt; python setup.py install
+run cd /usr/local/src/graphite-web && git checkout 0.9.x && pip install -r requirements.txt; python check-dependencies.py; python setup.py install
# Add graphite config
-add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json
-add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py
-add ./files/carbon.conf /opt/graphite/conf/carbon.conf
-add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf
-add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf
-add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py
+add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json
+add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py
+add ./files/carbon.conf /opt/graphite/conf/carbon.conf
+add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf
+add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf
+add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py
-run mkdir -p /opt/graphite/storage/whisper
-run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index
-run chown -R www-data /opt/graphite/storage
-run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper
-run chmod 0664 /opt/graphite/storage/graphite.db
-run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput
+run mkdir -p /opt/graphite/storage/whisper
+run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index
+run chown -R www-data /opt/graphite/storage
+run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper
+run chmod 0664 /opt/graphite/storage/graphite.db
+run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput
+
+add ./files/my_htpasswd /etc/nginx/.htpasswd
# Add system service config
-add ./files/nginx.conf /etc/nginx/nginx.conf
-add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
-
+add ./files/nginx.conf /etc/nginx/nginx.conf
+add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
+# Nginx
+#
# graphite
-expose 80
+expose 80
# Carbon line receiver port
-expose 2003
+expose 2003
# Carbon cache query port
-expose 7002
+expose 7002
-# Statsd UDP port
-expose 8125/udp
-# Statsd Management port
-expose 8126
-
-VOLUME ["/var/lib/elasticsearch"]
VOLUME ["/opt/graphite/storage/whisper"]
VOLUME ["/var/lib/log/supervisor"]
-cmd ["/usr/bin/supervisord"]
+cmd ["/usr/bin/supervisord"]
# vim:ts=8:noet:
diff --git a/docker/blocks/graphite/fig b/docker/blocks/graphite/fig
index 28e7d3c53a2..84da45341e1 100644
--- a/docker/blocks/graphite/fig
+++ b/docker/blocks/graphite/fig
@@ -1,4 +1,10 @@
graphite:
build: blocks/graphite
ports:
- - "8776:80"
+ - "8080:80"
+ - "2003:2003"
+ volumes:
+ - /var/docker/gfdev/graphite:/opt/graphite/storage/whisper
+ - /etc/localtime:/etc/localtime:ro
+ - /etc/timezone:/etc/timezone:ro
+
diff --git a/docker/blocks/graphite/files/my_htpasswd b/docker/blocks/graphite/files/my_htpasswd
new file mode 100644
index 00000000000..52a72d01b4c
--- /dev/null
+++ b/docker/blocks/graphite/files/my_htpasswd
@@ -0,0 +1 @@
+grafana:$apr1$4R/20xhC$8t37jPP5dbcLr48btdkU//
diff --git a/docker/blocks/graphite/files/supervisord.conf b/docker/blocks/graphite/files/supervisord.conf
index 25ba39c8819..c9812bb16dc 100644
--- a/docker/blocks/graphite/files/supervisord.conf
+++ b/docker/blocks/graphite/files/supervisord.conf
@@ -24,10 +24,3 @@ stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true
-[program:statsd]
-;user = www-data
-command = /usr/bin/node /src/statsd/stats.js /src/statsd/config.js
-stdout_logfile = /var/log/supervisor/%(program_name)s.log
-stderr_logfile = /var/log/supervisor/%(program_name)s.log
-autorestart = true
-
diff --git a/docker/blocks/influxdb/Dockerfile b/docker/blocks/influxdb/Dockerfile
deleted file mode 100644
index 69d10992464..00000000000
--- a/docker/blocks/influxdb/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-# influxdb
-
-FROM ubuntu
-
-RUN mkdir -p /opt/influxdb/shared/data
-
-ADD http://s3.amazonaws.com/influxdb/influxdb_0.8.8_amd64.deb /influx88.deb
-RUN dpkg -i /influx88.deb
-RUN rm -rf /opt/influxdb/shared/data
-
-ADD config.toml /opt/influxdb/shared/config.toml
-
-EXPOSE 8083 8086 2004
-
-ENTRYPOINT ["/usr/bin/influxdb"]
-CMD ["-config=/opt/influxdb/shared/config.toml"]
diff --git a/docker/blocks/influxdb/fig b/docker/blocks/influxdb/fig
index 3f247f756e2..931f8a2640a 100644
--- a/docker/blocks/influxdb/fig
+++ b/docker/blocks/influxdb/fig
@@ -1,5 +1,5 @@
influxdb:
- build: blocks/influxdb
+ image: tutum/influxdb:latest
ports:
- "2004:2004"
- "8083:8083"
diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md
index 7cdd8a872d5..4b0dff6c4b6 100644
--- a/docs/sources/reference/http_api.md
+++ b/docs/sources/reference/http_api.md
@@ -1422,6 +1422,34 @@ Keys:
}
}
+### Grafana Stats
+
+`GET /api/admin/stats`
+
+**Example Request**:
+
+ GET /api/admin/stats
+ Accept: application/json
+ Content-Type: application/json
+ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+**Example Response**:
+
+ HTTP/1.1 200
+ Content-Type: application/json
+
+ {
+ "user_count":2,
+ "org_count":1,
+ "dashboard_count":4,
+ "db_snapshot_count":2,
+ "db_tag_count":6,
+ "data_source_count":1,
+ "playlist_count":1,
+ "starred_db_count":2,
+ "grafana_admin_count":2
+ }
+
### Global Users
`POST /api/admin/users`
diff --git a/docs/sources/reference/singlestat.md b/docs/sources/reference/singlestat.md
index 91724e89882..954bfeed8b7 100644
--- a/docs/sources/reference/singlestat.md
+++ b/docs/sources/reference/singlestat.md
@@ -31,7 +31,7 @@ The coloring options of the Singlestat Panel config allow you to dynamically cha
1. `Background`: This checkbox applies the configured thresholds and colors to the entirety of the Singlestat Panel background.
2. `Value`: This checkbox applies the configured thresholds and colors to the summary stat.
-3. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right.
+3. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90.
4. `Colors`: Select a color and opacity
5. `Invert order`: This link toggles the threshold color order.For example: Green, Orange, Red () will become Red, Orange, Green (
).
diff --git a/pkg/api/admin_settings.go b/pkg/api/admin.go
similarity index 66%
rename from pkg/api/admin_settings.go
rename to pkg/api/admin.go
index 1f800cfe558..d7f5a240416 100644
--- a/pkg/api/admin_settings.go
+++ b/pkg/api/admin.go
@@ -3,7 +3,9 @@ package api
import (
"strings"
+ "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"
)
@@ -27,3 +29,15 @@ func AdminGetSettings(c *middleware.Context) {
c.JSON(200, settings)
}
+
+func AdminGetStats(c *middleware.Context) {
+
+ statsQuery := m.GetAdminStatsQuery{}
+
+ if err := bus.Dispatch(&statsQuery); err != nil {
+ c.JsonApiErr(500, "Failed to get admin stats from database", err)
+ return
+ }
+
+ c.JSON(200, statsQuery.Result)
+}
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 8bbb64eb1b2..99ab8a51ff2 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -40,6 +40,7 @@ func Register(r *macaron.Macaron) {
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
+ r.Get("/admin/stats", reqGrafanaAdmin, Index)
r.Get("/apps", reqSignedIn, Index)
r.Get("/apps/edit/*", reqSignedIn, Index)
@@ -210,6 +211,7 @@ func Register(r *macaron.Macaron) {
r.Delete("/users/:id", AdminDeleteUser)
r.Get("/users/:id/quotas", wrap(GetUserQuotas))
r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
+ r.Get("/stats", AdminGetStats)
}, reqGrafanaAdmin)
// rendering
diff --git a/pkg/metrics/report_usage.go b/pkg/metrics/report_usage.go
index abda18d12b7..c5c2afed7f7 100644
--- a/pkg/metrics/report_usage.go
+++ b/pkg/metrics/report_usage.go
@@ -55,6 +55,7 @@ func sendUsageStats() {
metrics["stats.dashboards.count"] = statsQuery.Result.DashboardCount
metrics["stats.users.count"] = statsQuery.Result.UserCount
metrics["stats.orgs.count"] = statsQuery.Result.OrgCount
+ metrics["stats.playlist.count"] = statsQuery.Result.PlaylistCount
dsStats := m.GetDataSourceStatsQuery{}
if err := bus.Dispatch(&dsStats); err != nil {
diff --git a/pkg/models/stats.go b/pkg/models/stats.go
index 6a060137ac7..63f946b956b 100644
--- a/pkg/models/stats.go
+++ b/pkg/models/stats.go
@@ -4,6 +4,7 @@ type SystemStats struct {
DashboardCount int
UserCount int
OrgCount int
+ PlaylistCount int
}
type DataSourceStats struct {
@@ -18,3 +19,19 @@ type GetSystemStatsQuery struct {
type GetDataSourceStatsQuery struct {
Result []*DataSourceStats
}
+
+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"`
+}
+
+type GetAdminStatsQuery struct {
+ Result *AdminStats
+}
diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go
index 044aa185f19..92efab2015d 100644
--- a/pkg/services/sqlstore/stats.go
+++ b/pkg/services/sqlstore/stats.go
@@ -8,6 +8,7 @@ import (
func init() {
bus.AddHandler("sql", GetSystemStats)
bus.AddHandler("sql", GetDataSourceStats)
+ bus.AddHandler("sql", GetAdminStats)
}
func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
@@ -34,7 +35,11 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
- ) AS dashboard_count
+ ) AS dashboard_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("playlist") + `
+ ) AS playlist_count
`
var stats m.SystemStats
@@ -46,3 +51,54 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
query.Result = &stats
return err
}
+
+func GetAdminStats(query *m.GetAdminStatsQuery) error {
+ var rawSql = `SELECT
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("user") + `
+ ) AS user_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("org") + `
+ ) AS org_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("dashboard") + `
+ ) AS dashboard_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("dashboard_snapshot") + `
+ ) AS db_snapshot_count,
+ (
+ SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` ))
+ FROM ` + dialect.Quote("dashboard_tag") + `
+ ) AS db_tag_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("data_source") + `
+ ) AS data_source_count,
+ (
+ SELECT COUNT(*)
+ FROM ` + dialect.Quote("playlist") + `
+ ) AS playlist_count,
+ (
+ 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
+ `
+
+ var stats m.AdminStats
+ _, err := x.Sql(rawSql).Get(&stats)
+ if err != nil {
+ return err
+ }
+
+ query.Result = &stats
+ return err
+}
diff --git a/public/app/partials/search.html b/public/app/core/components/search/search.html
similarity index 50%
rename from public/app/partials/search.html
rename to public/app/core/components/search/search.html
index fab8fd9291a..7a78f1a4a3e 100644
--- a/public/app/partials/search.html
+++ b/public/app/core/components/search/search.html
@@ -1,24 +1,22 @@
-
Name | +Value | +
---|---|
Total dashboards | +{{ctrl.stats.dashboard_count}} | +
Total users | +{{ctrl.stats.user_count}} | +
Total grafana admins | +{{ctrl.stats.grafana_admin_count}} | +
Total organizations | +{{ctrl.stats.org_count}} | +
Total datasources | +{{ctrl.stats.data_source_count}} | +
Total playlists | +{{ctrl.stats.playlist_count}} | +
Total snapshots | +{{ctrl.stats.db_snapshot_count}} | +
Total dashboard tags | +{{ctrl.stats.db_tag_count}} | +
Total starred dashboards | +{{ctrl.stats.starred_db_count}} | +