Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
85ad5f1d
Commit
85ad5f1d
authored
Jan 28, 2016
by
bergquist
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into playlist_tags
parents
a7de2cea
64fa9a63
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
272 additions
and
15 deletions
+272
-15
CHANGELOG.md
+1
-0
circle.yml
+7
-0
docs/sources/reference/http_api.md
+28
-0
pkg/api/admin.go
+14
-0
pkg/api/api.go
+2
-0
pkg/models/stats.go
+16
-0
pkg/services/sqlstore/stats.go
+52
-0
public/app/core/components/sidemenu/sidemenu.ts
+7
-0
public/app/core/routes/all.js
+5
-0
public/app/features/admin/adminStatsCtrl.ts
+18
-0
public/app/features/admin/all.js
+1
-0
public/app/features/admin/partials/stats.html
+60
-0
public/app/plugins/datasource/opentsdb/datasource.js
+3
-0
public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts
+2
-2
public/app/plugins/panel/graph/graph.js
+8
-5
public/app/plugins/panel/graph/specs/graph_specs.ts
+32
-4
public/app/plugins/panel/singlestat/module.ts
+2
-1
public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts
+3
-3
trigger_grafana_packer.sh
+11
-0
No files found.
CHANGELOG.md
View file @
85ad5f1d
...
...
@@ -14,6 +14,7 @@
*
**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
)
...
...
circle.yml
View file @
85ad5f1d
...
...
@@ -27,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}
docs/sources/reference/http_api.md
View file @
85ad5f1d
...
...
@@ -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`
...
...
pkg/api/admin
_settings
.go
→
pkg/api/admin.go
View file @
85ad5f1d
...
...
@@ -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
)
}
pkg/api/api.go
View file @
85ad5f1d
...
...
@@ -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
...
...
pkg/models/stats.go
View file @
85ad5f1d
...
...
@@ -19,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
}
pkg/services/sqlstore/stats.go
View file @
85ad5f1d
...
...
@@ -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
{
...
...
@@ -50,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
}
public/app/core/components/sidemenu/sidemenu.ts
View file @
85ad5f1d
...
...
@@ -108,6 +108,12 @@ export class SideMenuCtrl {
});
this
.
mainLinks
.
push
({
text
:
"Grafana stats"
,
icon
:
"fa fa-fw fa-bar-chart"
,
url
:
this
.
getUrl
(
"/admin/stats"
),
});
this
.
mainLinks
.
push
({
text
:
"Global Users"
,
icon
:
"fa fa-fw fa-user"
,
url
:
this
.
getUrl
(
"/admin/users"
),
...
...
@@ -118,6 +124,7 @@ export class SideMenuCtrl {
icon
:
"fa fa-fw fa-users"
,
url
:
this
.
getUrl
(
"/admin/orgs"
),
});
}
updateMenu
()
{
...
...
public/app/core/routes/all.js
View file @
85ad5f1d
...
...
@@ -112,6 +112,11 @@ define([
templateUrl
:
'app/features/admin/partials/edit_org.html'
,
controller
:
'AdminEditOrgCtrl'
,
})
.
when
(
'/admin/stats'
,
{
templateUrl
:
'app/features/admin/partials/stats.html'
,
controller
:
'AdminStatsCtrl'
,
controllerAs
:
'ctrl'
,
})
.
when
(
'/login'
,
{
templateUrl
:
'app/partials/login.html'
,
controller
:
'LoginCtrl'
,
...
...
public/app/features/admin/adminStatsCtrl.ts
0 → 100644
View file @
85ad5f1d
///<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
);
public/app/features/admin/all.js
View file @
85ad5f1d
...
...
@@ -4,4 +4,5 @@ define([
'./adminEditOrgCtrl'
,
'./adminEditUserCtrl'
,
'./adminSettingsCtrl'
,
'./adminStatsCtrl'
,
],
function
()
{});
public/app/features/admin/partials/stats.html
0 → 100644
View file @
85ad5f1d
<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>
public/app/plugins/datasource/opentsdb/datasource.js
View file @
85ad5f1d
...
...
@@ -72,6 +72,9 @@ function (angular, _, dateMath) {
data
:
reqBody
};
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
// go as POST rather than OPTIONS+POST
options
.
headers
=
{
'Content-Type'
:
'application/x-www-form-urlencoded'
};
return
backendSrv
.
datasourceRequest
(
options
);
};
...
...
public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts
View file @
85ad5f1d
...
...
@@ -36,14 +36,14 @@ describe('opentsdb', function() {
expect
(
requestOptions
.
params
.
q
).
to
.
be
(
'pew'
);
});
it
(
'tag_names(cpu) should generate looku
query'
,
function
()
{
it
(
'tag_names(cpu) should generate looku
p
query'
,
function
()
{
ctx
.
ds
.
metricFindQuery
(
'tag_names(cpu)'
).
then
(
function
(
data
)
{
results
=
data
;
});
ctx
.
$rootScope
.
$apply
();
expect
(
requestOptions
.
url
).
to
.
be
(
'/api/search/lookup'
);
expect
(
requestOptions
.
params
.
m
).
to
.
be
(
'cpu'
);
});
it
(
'tag_values(cpu, test) should generate looku
query'
,
function
()
{
it
(
'tag_values(cpu, test) should generate looku
p
query'
,
function
()
{
ctx
.
ds
.
metricFindQuery
(
'tag_values(cpu, hostname)'
).
then
(
function
(
data
)
{
results
=
data
;
});
ctx
.
$rootScope
.
$apply
();
expect
(
requestOptions
.
url
).
to
.
be
(
'/api/search/lookup'
);
...
...
public/app/plugins/panel/graph/graph.js
View file @
85ad5f1d
...
...
@@ -296,7 +296,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
max
:
max
,
label
:
"Datetime"
,
ticks
:
ticks
,
timeformat
:
time_format
(
scope
.
interval
,
ticks
,
min
,
max
),
timeformat
:
time_format
(
ticks
,
min
,
max
),
};
}
...
...
@@ -436,20 +436,23 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
};
}
function
time_format
(
interval
,
ticks
,
min
,
max
)
{
function
time_format
(
ticks
,
min
,
max
)
{
if
(
min
&&
max
&&
ticks
)
{
var
secPerTick
=
((
max
-
min
)
/
ticks
)
/
1000
;
var
range
=
max
-
min
;
var
secPerTick
=
(
range
/
ticks
)
/
1000
;
var
oneDay
=
86400000
;
var
oneYear
=
31536000000
;
if
(
secPerTick
<=
45
)
{
return
"%H:%M:%S"
;
}
if
(
secPerTick
<=
7200
)
{
if
(
secPerTick
<=
7200
||
range
<=
oneDay
)
{
return
"%H:%M"
;
}
if
(
secPerTick
<=
80000
)
{
return
"%m/%d %H:%M"
;
}
if
(
secPerTick
<=
2419200
)
{
if
(
secPerTick
<=
2419200
||
range
<=
oneYear
)
{
return
"%m/%d"
;
}
return
"%Y-%m"
;
...
...
public/app/plugins/panel/graph/specs/graph_specs.ts
View file @
85ad5f1d
...
...
@@ -7,12 +7,13 @@ import angular from 'angular';
import
$
from
'jquery'
;
import
helpers
from
'../../../../../test/specs/helpers'
;
import
TimeSeries
from
'../../../../core/time_series2'
;
import
moment
from
'moment'
;
describe
(
'grafanaGraph'
,
function
()
{
beforeEach
(
angularMocks
.
module
(
'grafana.directives'
));
function
graphScenario
(
desc
,
func
)
{
function
graphScenario
(
desc
,
func
,
elementWidth
=
500
)
{
describe
(
desc
,
function
()
{
var
ctx
:
any
=
{};
...
...
@@ -24,7 +25,7 @@ describe('grafanaGraph', function() {
beforeEach
(
angularMocks
.
inject
(
function
(
$rootScope
,
$compile
)
{
var
scope
=
$rootScope
.
$new
();
var
element
=
angular
.
element
(
"<div style='width:
500
px' grafana-graph><div>"
);
var
element
=
angular
.
element
(
"<div style='width:
"
+
elementWidth
+
"
px' grafana-graph><div>"
);
scope
.
height
=
'200px'
;
scope
.
panel
=
{
...
...
@@ -43,8 +44,8 @@ describe('grafanaGraph', function() {
scope
.
hiddenSeries
=
{};
scope
.
dashboard
=
{
timezone
:
'browser'
};
scope
.
range
=
{
from
:
new
Date
(
'2014-08-09 10:00:00'
),
to
:
new
Date
(
'2014-09-09 13:00:00'
)
from
:
moment
([
2015
,
1
,
1
,
10
]
),
to
:
moment
([
2015
,
1
,
1
,
22
]
)
};
ctx
.
data
=
[];
ctx
.
data
.
push
(
new
TimeSeries
({
...
...
@@ -227,4 +228,31 @@ describe('grafanaGraph', function() {
expect
(
axis
.
tickFormatter
(
100
,
axis
)).
to
.
be
(
"100%"
);
});
});
graphScenario
(
'when panel too narrow to show x-axis dates in same granularity as wide panels'
,
function
(
ctx
)
{
describe
(
'and the range is less than 24 hours'
,
function
()
{
ctx
.
setup
(
function
(
scope
)
{
scope
.
range
.
from
=
moment
([
2015
,
1
,
1
,
10
]);
scope
.
range
.
to
=
moment
([
2015
,
1
,
1
,
22
]);
});
it
(
'should format dates as hours minutes'
,
function
()
{
var
axis
=
ctx
.
plotOptions
.
xaxis
;
expect
(
axis
.
timeformat
).
to
.
be
(
'%H:%M'
);
});
});
describe
(
'and the range is less than one year'
,
function
()
{
ctx
.
setup
(
function
(
scope
)
{
scope
.
range
.
from
=
moment
([
2015
,
1
,
1
]);
scope
.
range
.
to
=
moment
([
2015
,
11
,
20
]);
});
it
(
'should format dates as month days'
,
function
()
{
var
axis
=
ctx
.
plotOptions
.
xaxis
;
expect
(
axis
.
timeformat
).
to
.
be
(
'%m/%d'
);
});
});
},
10
);
});
public/app/plugins/panel/singlestat/module.ts
View file @
85ad5f1d
...
...
@@ -7,6 +7,7 @@ import {SingleStatCtrl} from './controller';
angular
.
module
(
'grafana.directives'
).
directive
(
'singleStatPanel'
,
singleStatPanel
);
/** @ngInject */
function
singleStatPanel
(
$location
,
linkSrv
,
$timeout
,
templateSrv
)
{
'use strict'
;
return
{
...
...
@@ -221,7 +222,7 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) {
function
getColorForValue
(
data
,
value
)
{
for
(
var
i
=
data
.
thresholds
.
length
;
i
>
0
;
i
--
)
{
if
(
value
>=
data
.
thresholds
[
i
])
{
if
(
value
>=
data
.
thresholds
[
i
-
1
])
{
return
data
.
colorMap
[
i
];
}
}
...
...
public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts
View file @
85ad5f1d
...
...
@@ -7,7 +7,7 @@ describe('grafanaSingleStat', function() {
describe
(
'positive thresholds'
,
()
=>
{
var
data
:
any
=
{
colorMap
:
[
'green'
,
'yellow'
,
'red'
],
thresholds
:
[
0
,
20
,
50
]
thresholds
:
[
20
,
50
]
};
it
(
'5 should return green'
,
()
=>
{
...
...
@@ -29,7 +29,7 @@ describe('grafanaSingleStat', function() {
describe
(
'negative thresholds'
,
()
=>
{
var
data
:
any
=
{
colorMap
:
[
'green'
,
'yellow'
,
'red'
],
thresholds
:
[
-
20
,
0
,
20
]
thresholds
:
[
0
,
20
]
};
it
(
'-30 should return green'
,
()
=>
{
...
...
@@ -48,7 +48,7 @@ describe('grafanaSingleStat', function() {
describe
(
'negative thresholds'
,
()
=>
{
var
data
:
any
=
{
colorMap
:
[
'green'
,
'yellow'
,
'red'
],
thresholds
:
[
-
40
,
-
27
,
20
]
thresholds
:
[
-
27
,
20
]
};
it
(
'-30 should return green'
,
()
=>
{
...
...
trigger_grafana_packer.sh
0 → 100755
View file @
85ad5f1d
#!/bin/bash
_circle_token
=
$1
trigger_build_url
=
https://circleci.com/api/v1/project/grafana/grafana-packer/tree/master?circle-token
=
${
_circle_token
}
curl
\
--header
"Accept: application/json"
\
--header
"Content-Type: application/json"
\
--request
POST
${
trigger_build_url
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment