Commit 251bf7a2 by Carl Bergquist

Merge pull request #3840 from utkarshcmu/stats

Grafana stats view as mentioned in #3812
parents a1b5aae9 e59b0c06
...@@ -1422,6 +1422,34 @@ Keys: ...@@ -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 ### Global Users
`POST /api/admin/users` `POST /api/admin/users`
......
...@@ -3,7 +3,9 @@ package api ...@@ -3,7 +3,9 @@ package api
import ( import (
"strings" "strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
...@@ -27,3 +29,15 @@ func AdminGetSettings(c *middleware.Context) { ...@@ -27,3 +29,15 @@ func AdminGetSettings(c *middleware.Context) {
c.JSON(200, settings) 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)
}
...@@ -40,6 +40,7 @@ func Register(r *macaron.Macaron) { ...@@ -40,6 +40,7 @@ func Register(r *macaron.Macaron) {
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs", reqGrafanaAdmin, Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
r.Get("/admin/stats", reqGrafanaAdmin, Index)
r.Get("/apps", reqSignedIn, Index) r.Get("/apps", reqSignedIn, Index)
r.Get("/apps/edit/*", reqSignedIn, Index) r.Get("/apps/edit/*", reqSignedIn, Index)
...@@ -210,6 +211,7 @@ func Register(r *macaron.Macaron) { ...@@ -210,6 +211,7 @@ func Register(r *macaron.Macaron) {
r.Delete("/users/:id", AdminDeleteUser) r.Delete("/users/:id", AdminDeleteUser)
r.Get("/users/:id/quotas", wrap(GetUserQuotas)) r.Get("/users/:id/quotas", wrap(GetUserQuotas))
r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
r.Get("/stats", AdminGetStats)
}, reqGrafanaAdmin) }, reqGrafanaAdmin)
// rendering // rendering
......
...@@ -19,3 +19,19 @@ type GetSystemStatsQuery struct { ...@@ -19,3 +19,19 @@ type GetSystemStatsQuery struct {
type GetDataSourceStatsQuery struct { type GetDataSourceStatsQuery struct {
Result []*DataSourceStats 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
}
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
func init() { func init() {
bus.AddHandler("sql", GetSystemStats) bus.AddHandler("sql", GetSystemStats)
bus.AddHandler("sql", GetDataSourceStats) bus.AddHandler("sql", GetDataSourceStats)
bus.AddHandler("sql", GetAdminStats)
} }
func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
...@@ -50,3 +51,54 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { ...@@ -50,3 +51,54 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
query.Result = &stats query.Result = &stats
return err 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(*)
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
}
...@@ -108,6 +108,12 @@ export class SideMenuCtrl { ...@@ -108,6 +108,12 @@ export class SideMenuCtrl {
}); });
this.mainLinks.push({ this.mainLinks.push({
text: "Grafana stats",
icon: "fa fa-fw fa-bar-chart",
url: this.getUrl("/admin/stats"),
});
this.mainLinks.push({
text: "Global Users", text: "Global Users",
icon: "fa fa-fw fa-user", icon: "fa fa-fw fa-user",
url: this.getUrl("/admin/users"), url: this.getUrl("/admin/users"),
...@@ -118,6 +124,7 @@ export class SideMenuCtrl { ...@@ -118,6 +124,7 @@ export class SideMenuCtrl {
icon: "fa fa-fw fa-users", icon: "fa fa-fw fa-users",
url: this.getUrl("/admin/orgs"), url: this.getUrl("/admin/orgs"),
}); });
} }
updateMenu() { updateMenu() {
......
...@@ -112,6 +112,11 @@ define([ ...@@ -112,6 +112,11 @@ define([
templateUrl: 'app/features/admin/partials/edit_org.html', templateUrl: 'app/features/admin/partials/edit_org.html',
controller : 'AdminEditOrgCtrl', controller : 'AdminEditOrgCtrl',
}) })
.when('/admin/stats', {
templateUrl: 'app/features/admin/partials/stats.html',
controller : 'AdminStatsCtrl',
controllerAs: 'ctrl',
})
.when('/login', { .when('/login', {
templateUrl: 'app/partials/login.html', templateUrl: 'app/partials/login.html',
controller : 'LoginCtrl', controller : 'LoginCtrl',
......
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
export class AdminStatsCtrl {
stats: any;
/** @ngInject */
constructor(private backendSrv: any) {}
init() {
this.backendSrv.get('/api/admin/stats').then(stats => {
this.stats = stats;
});
}
}
angular.module('grafana.controllers').controller('AdminStatsCtrl', AdminStatsCtrl);
...@@ -4,4 +4,5 @@ define([ ...@@ -4,4 +4,5 @@ define([
'./adminEditOrgCtrl', './adminEditOrgCtrl',
'./adminEditUserCtrl', './adminEditUserCtrl',
'./adminSettingsCtrl', './adminSettingsCtrl',
'./adminStatsCtrl',
], function () {}); ], function () {});
<topnav icon="fa fa-fw fa-bar-chart" title="Grafana stats" subnav="true">
<ul class="nav">
<li class="active"><a href="admin/stats">Overview</a></li>
</ul>
</topnav>
<div class="page-container">
<div class="page-wide" ng-init="ctrl.init()">
<h1>
Overview
</h1>
<table class="filter-table form-inline">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Total dashboards</td>
<td>{{ctrl.stats.dashboard_count}}</td>
</tr>
<tr>
<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>
</tr>
<tr>
<td>Total datasources</td>
<td>{{ctrl.stats.data_source_count}}</td>
</tr>
<tr>
<td>Total playlists</td>
<td>{{ctrl.stats.playlist_count}}</td>
</tr>
<tr>
<td>Total snapshots</td>
<td>{{ctrl.stats.db_snapshot_count}}</td>
</tr>
<tr>
<td>Total dashboard tags</td>
<td>{{ctrl.stats.db_tag_count}}</td>
</tr>
<tr>
<td>Total starred dashboards</td>
<td>{{ctrl.stats.starred_db_count}}</td>
</tr>
</tbody>
</table>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment