mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'upstream/master'
Conflicts: public/app/plugins/datasource/opentsdb/datasource.js
This commit is contained in:
commit
76c18e50a4
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,4 +26,5 @@ public/css/*.min.css
|
||||
|
||||
conf/custom.ini
|
||||
fig.yml
|
||||
profile.cov
|
||||
|
||||
|
49
CHANGELOG.md
49
CHANGELOG.md
@ -1,3 +1,52 @@
|
||||
# 2.1.0 (unreleased - master branch)
|
||||
|
||||
**Data sources**
|
||||
- [Issue #1525](https://github.com/grafana/grafana/issues/1525). InfluxDB: Full support for InfluxDB 0.9 with new adapted query editor
|
||||
- [Issue #2191](https://github.com/grafana/grafana/issues/2191). KariosDB: Grafana now ships with a KariosDB data source plugin, thx @masaori335
|
||||
- [Issue #1177](https://github.com/grafana/grafana/issues/1177). OpenTSDB: Limit tags by metric, OpenTSDB config option tsd.core.meta.enable_realtime_ts must enabled for OpenTSDB lookup api
|
||||
- [Issue #1250](https://github.com/grafana/grafana/issues/1250). OpenTSDB: Support for template variable values lookup queries
|
||||
|
||||
**New dashboard features**
|
||||
- [Issue #1144](https://github.com/grafana/grafana/issues/1144). Templating: You can now select multiple template variables values at the same time.
|
||||
- [Issue #1922](https://github.com/grafana/grafana/issues/1922). Templating: Specify multiple variable values via URL params.
|
||||
- [Issue #1888](https://github.com/grafana/grafana/issues/1144). Templating: Repeat panel or row for each selected template variable value
|
||||
- [Issue #1888](https://github.com/grafana/grafana/issues/1944). Dashboard: Custom Navigation links & dynamic links to related dashboards
|
||||
- [Issue #590](https://github.com/grafana/grafana/issues/590). Graph: Define series color using regex rule
|
||||
- [Issue #2162](https://github.com/grafana/grafana/issues/2162). Graph: New series style override, negative-y transform and stack groups
|
||||
- [Issue #2096](https://github.com/grafana/grafana/issues/2096). Dashboard list panel: Now supports search by multiple tags
|
||||
- [Issue #2203](https://github.com/grafana/grafana/issues/2203). Singlestat: Now support string values
|
||||
|
||||
**User or Organization admin**
|
||||
- [Issue #1899](https://github.com/grafana/grafana/issues/1899). Organization: You can now update the organization user role directly (without removing and readding the organization user).
|
||||
- [Issue #2088](https://github.com/grafana/grafana/issues/2088). Roles: New user role `Read Only Editor` that replaces the old `Viewer` role behavior
|
||||
|
||||
**Backend**
|
||||
- [Issue #2218](https://github.com/grafana/grafana/issues/2218). Auth: You can now authenicate against api with username / password using basic auth
|
||||
- [Issue #2095](https://github.com/grafana/grafana/issues/2095). Search: Search now supports filtering by multiple dashboard tags
|
||||
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
|
||||
- [Issue #2052](https://github.com/grafana/grafana/issues/2052). Github OAuth: You can now configure a Github organization requirement, thx @indrekj
|
||||
- [Issue #1891](https://github.com/grafana/grafana/issues/1891). Security: New config option to disable the use of gravatar for profile images
|
||||
- [Issue #1921](https://github.com/grafana/grafana/issues/1921). Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER)
|
||||
- [Issue #960](https://github.com/grafana/grafana/issues/960). Search: Backend can now index a folder with json files, will be available in search (saving back to folder is not supported, this feature is meant for static generated json dashboards)
|
||||
|
||||
**Breaking changes**
|
||||
- [Issue #1826](https://github.com/grafana/grafana/issues/1826). User role 'Viewer' are now prohibited from entering edit mode (and doing other transient dashboard edits). A new role `Read Only Editor` will replace the old Viewer behavior
|
||||
- [Issue #1928](https://github.com/grafana/grafana/issues/1928). HTTP API: GET /api/dashboards/db/:slug response changed property `model` to `dashboard` to match the POST request nameing
|
||||
- Backend render URL changed from `/render/dashboard/solo` `render/dashboard-solo/` (in order to have consistent dashboard url `/dashboard/:type/:slug`)
|
||||
- Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI
|
||||
- Datasource HTTP api breaking change, ADD datasource is now POST /api/datasources/, update is now PUT /api/datasources/:id
|
||||
|
||||
**Fixes**
|
||||
- [Issue #2185](https://github.com/grafana/grafana/issues/2185). Graph: fixed PNG rendering of panels with legend table to the right
|
||||
- [Issue #2163](https://github.com/grafana/grafana/issues/2163). Backend: Load dashboards with capital letters in the dashboard url slug (url id)
|
||||
|
||||
# 2.0.3 (unreleased - 2.0.x branch)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed
|
||||
- [Issue #1857](https://github.com/grafana/grafana/issues/1857). /api/login/ping Fix for issue when behind reverse proxy and subpath
|
||||
- [Issue #1863](https://github.com/grafana/grafana/issues/1863). MySQL: Dashboard.data column type changed to mediumtext (sql migration added)
|
||||
|
||||
# 2.0.2 (2015-04-22)
|
||||
|
||||
**Fixes**
|
||||
|
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gosimple/slug",
|
||||
"Rev": "a2392a4a87fa0366cbff131d3fd421f83f52492f"
|
||||
"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtolds/gls",
|
||||
@ -52,6 +52,10 @@
|
||||
"ImportPath": "github.com/mattn/go-sqlite3",
|
||||
"Rev": "e28cd440fabdd39b9520344bc26829f61db40ece"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rainycape/unidecode",
|
||||
"Rev": "836ef0a715aedf08a12d595ed73ec8ed5b288cac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/smartystreets/goconvey/convey",
|
||||
"Comment": "1.5.0-356-gfbc0a1c",
|
||||
@ -83,14 +87,6 @@
|
||||
"ImportPath": "gopkg.in/redis.v2",
|
||||
"Comment": "v2.3.2",
|
||||
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/pool.v1",
|
||||
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/unidecode.v1",
|
||||
"Rev": "4deae2c05236b41cc39f8144ac87a837ba974d40"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
3
Godeps/_workspace/src/github.com/gosimple/slug/slug.go
generated
vendored
3
Godeps/_workspace/src/github.com/gosimple/slug/slug.go
generated
vendored
@ -6,9 +6,10 @@
|
||||
package slug
|
||||
|
||||
import (
|
||||
"gopkgs.com/unidecode.v1"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/rainycape/unidecode"
|
||||
)
|
||||
|
||||
var (
|
||||
|
6
Godeps/_workspace/src/github.com/rainycape/unidecode/README.md
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/rainycape/unidecode/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
unidecode
|
||||
=========
|
||||
|
||||
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
|
||||
|
||||
[](https://godoc.org/github.com/rainycape/unidecode)
|
@ -5,12 +5,9 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
decoded = false
|
||||
mutex sync.Mutex
|
||||
transliterations [65536][]rune
|
||||
transCount = rune(len(transliterations))
|
||||
getUint16 = binary.LittleEndian.Uint16
|
@ -4,15 +4,15 @@
|
||||
package unidecode
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"gopkgs.com/pool.v1"
|
||||
)
|
||||
|
||||
const pooledCapacity = 64
|
||||
|
||||
var (
|
||||
slicePool = pool.New(0)
|
||||
slicePool sync.Pool
|
||||
decodingOnce sync.Once
|
||||
)
|
||||
|
||||
// Unidecode implements a unicode transliterator, which
|
||||
@ -23,14 +23,7 @@ var (
|
||||
// with their closest ASCII counterparts.
|
||||
// e.g. Unicode("áéíóú") => "aeiou"
|
||||
func Unidecode(s string) string {
|
||||
if !decoded {
|
||||
mutex.Lock()
|
||||
if !decoded {
|
||||
decodeTransliterations()
|
||||
decoded = true
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
decodingOnce.Do(decodeTransliterations)
|
||||
l := len(s)
|
||||
var r []rune
|
||||
if l > pooledCapacity {
|
13
Godeps/_workspace/src/gopkgs.com/pool.v1/README.md
generated
vendored
13
Godeps/_workspace/src/gopkgs.com/pool.v1/README.md
generated
vendored
@ -1,13 +0,0 @@
|
||||
pool
|
||||
====
|
||||
|
||||
sync.Pool compatibility layer for for Go - falls back to a channel based pool in Go < 1.3
|
||||
|
||||
|
||||
Please, use the following import path to ensure a stable API:
|
||||
|
||||
```go
|
||||
import "gopkgs.com/pool.v1"
|
||||
```
|
||||
|
||||
View other available versions, documentation and examples at http://gopkgs.com/pool
|
3
Godeps/_workspace/src/gopkgs.com/pool.v1/doc.go
generated
vendored
3
Godeps/_workspace/src/gopkgs.com/pool.v1/doc.go
generated
vendored
@ -1,3 +0,0 @@
|
||||
// Package pool provides a sync.Pool compatibility layer, which
|
||||
// falls back to a channel based pool on Go < 1.3.
|
||||
package pool
|
23
Godeps/_workspace/src/gopkgs.com/pool.v1/example_test.go
generated
vendored
23
Godeps/_workspace/src/gopkgs.com/pool.v1/example_test.go
generated
vendored
@ -1,23 +0,0 @@
|
||||
package pool_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkgs.com/pool.v1"
|
||||
)
|
||||
|
||||
func ExamplePool() {
|
||||
p := pool.New(0)
|
||||
p.Put("Hello")
|
||||
fmt.Println(p.Get())
|
||||
// OutPut: Hello
|
||||
}
|
||||
|
||||
func ExamplePoolNew() {
|
||||
p := pool.New(0)
|
||||
p.New = func() interface{} {
|
||||
return "World!"
|
||||
}
|
||||
fmt.Println(p.Get())
|
||||
// OutPut: World!
|
||||
}
|
24
Godeps/_workspace/src/gopkgs.com/pool.v1/gopkgs.go
generated
vendored
24
Godeps/_workspace/src/gopkgs.com/pool.v1/gopkgs.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// gopkgs.go: v1
|
||||
|
||||
// NOTE: This file is autogenerated by gopkgs.com.
|
||||
const (
|
||||
goPkgsSrcPath = "github.com/rainycape/pool"
|
||||
goPkgsName = "pool"
|
||||
goPkgsErrFmt = "invalid import path %s - please use gopkgs.com/%s.v1 or see http://gopkgs.com/%s"
|
||||
)
|
||||
|
||||
type goPkgsCheck struct{}
|
||||
|
||||
func init() {
|
||||
typ := reflect.TypeOf(goPkgsCheck{})
|
||||
if typ.PkgPath() == goPkgsSrcPath {
|
||||
panic(fmt.Errorf(goPkgsErrFmt, typ.PkgPath(), goPkgsName, goPkgsName))
|
||||
}
|
||||
}
|
37
Godeps/_workspace/src/gopkgs.com/pool.v1/pool.go
generated
vendored
37
Godeps/_workspace/src/gopkgs.com/pool.v1/pool.go
generated
vendored
@ -1,37 +0,0 @@
|
||||
// +build go1.3,!appengine
|
||||
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Pool is a thin compatibility type to allow Go
|
||||
// libraries to use the new sync.Pool in Go 1.3,
|
||||
// while remaining compatible with lower Go versions.
|
||||
// For more information, see the sync.Pool type.
|
||||
type Pool sync.Pool
|
||||
|
||||
// New returns a new Pool. The size argument is
|
||||
// ignored on Go >= 1.3. In Go < 1.3, if size is
|
||||
// zero, it's set to runtime.GOMAXPROCS(0) * 2.
|
||||
func New(size int) *Pool {
|
||||
return &Pool{}
|
||||
}
|
||||
|
||||
// Get returns an arbitrary previously Put value, removing
|
||||
// it from the pool, or nil if there are no such values. Note
|
||||
// that callers should not assume anything about the Get return
|
||||
// value, since the runtime might decide to collect the elements
|
||||
// from the pool at any time.
|
||||
//
|
||||
// If there are no elements to return and the New() field is non-nil,
|
||||
// Get returns the result of calling it.
|
||||
func (p *Pool) Get() interface{} {
|
||||
return (*sync.Pool)(p).Get()
|
||||
}
|
||||
|
||||
// Put adds x to the pool.
|
||||
func (p *Pool) Put(x interface{}) {
|
||||
(*sync.Pool)(p).Put(x)
|
||||
}
|
57
Godeps/_workspace/src/gopkgs.com/pool.v1/pool_go1.2.go
generated
vendored
57
Godeps/_workspace/src/gopkgs.com/pool.v1/pool_go1.2.go
generated
vendored
@ -1,57 +0,0 @@
|
||||
// +build !go1.3 appengine
|
||||
|
||||
package pool
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Pool is a thin compatibility type to allow Go
|
||||
// libraries to use the new sync.Pool in Go 1.3,
|
||||
// while remaining compatible with lower Go versions.
|
||||
// For more information, see the sync.Pool type.
|
||||
type Pool struct {
|
||||
ch chan interface{}
|
||||
// New specifies a function to generate
|
||||
// a new value, when Get would otherwise
|
||||
// return nil.
|
||||
New func() interface{}
|
||||
}
|
||||
|
||||
// New returns a new Pool. The size argument is
|
||||
// ignored on Go >= 1.3. In Go < 1.3, if size is
|
||||
// zero, it's set to runtime.GOMAXPROCS(0) * 2.
|
||||
func New(size int) *Pool {
|
||||
if size == 0 {
|
||||
size = runtime.GOMAXPROCS(0) * 2
|
||||
}
|
||||
return &Pool{ch: make(chan interface{}, size)}
|
||||
}
|
||||
|
||||
// Get returns an arbitrary previously Put value, removing
|
||||
// it from the pool, or nil if there are no such values. Note
|
||||
// that callers should not assume anything about the Get return
|
||||
// value, since the runtime might decide to collect the elements
|
||||
// from the pool at any time.
|
||||
//
|
||||
// If there are no elements to return and the New() field is non-nil,
|
||||
// Get returns the result of calling it.
|
||||
func (p *Pool) Get() interface{} {
|
||||
select {
|
||||
case x := <-p.ch:
|
||||
return x
|
||||
default:
|
||||
}
|
||||
if p.New != nil {
|
||||
return p.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put adds x to the pool.
|
||||
func (p *Pool) Put(x interface{}) {
|
||||
select {
|
||||
case p.ch <- x:
|
||||
default:
|
||||
}
|
||||
}
|
23
Godeps/_workspace/src/gopkgs.com/unidecode.v1/.gitignore
generated
vendored
23
Godeps/_workspace/src/gopkgs.com/unidecode.v1/.gitignore
generated
vendored
@ -1,23 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
201
Godeps/_workspace/src/gopkgs.com/unidecode.v1/LICENSE
generated
vendored
201
Godeps/_workspace/src/gopkgs.com/unidecode.v1/LICENSE
generated
vendored
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
12
Godeps/_workspace/src/gopkgs.com/unidecode.v1/README.md
generated
vendored
12
Godeps/_workspace/src/gopkgs.com/unidecode.v1/README.md
generated
vendored
@ -1,12 +0,0 @@
|
||||
unidecode
|
||||
=========
|
||||
|
||||
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
|
||||
|
||||
Please, use the following import path to ensure a stable API:
|
||||
|
||||
```go
|
||||
import "gopkgs.com/unidecode.v1"
|
||||
```
|
||||
|
||||
View other available versions, documentation and examples at http://gopkgs.com/unidecode
|
24
Godeps/_workspace/src/gopkgs.com/unidecode.v1/gopkgs.go
generated
vendored
24
Godeps/_workspace/src/gopkgs.com/unidecode.v1/gopkgs.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
package unidecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// gopkgs.go: v1
|
||||
|
||||
// NOTE: This file is autogenerated by gopkgs.com.
|
||||
const (
|
||||
goPkgsSrcPath = "github.com/rainycape/unidecode"
|
||||
goPkgsName = "unidecode"
|
||||
goPkgsErrFmt = "invalid import path %s - please use gopkgs.com/%s.v1 or see http://gopkgs.com/%s"
|
||||
)
|
||||
|
||||
type goPkgsCheck struct{}
|
||||
|
||||
func init() {
|
||||
typ := reflect.TypeOf(goPkgsCheck{})
|
||||
if typ.PkgPath() == goPkgsSrcPath {
|
||||
panic(fmt.Errorf(goPkgsErrFmt, typ.PkgPath(), goPkgsName, goPkgsName))
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ go get github.com/grafana/grafana
|
||||
```
|
||||
cd $GOPATH/src/github.com/grafana/grafana
|
||||
go run build.go setup (only needed once to install godep)
|
||||
godep restore (will pull down all golang lib dependecies in your current GOPATH)
|
||||
godep restore (will pull down all golang lib dependencies in your current GOPATH)
|
||||
go build .
|
||||
```
|
||||
|
||||
@ -125,8 +125,9 @@ You only need to add the options you want to override. Config files are applied
|
||||
2. dev.ini (if found)
|
||||
3. custom.ini
|
||||
|
||||
## Create a pull requests
|
||||
Before or after your create a pull requests, sign the [contributor license aggrement](/docs/contributing/cla.html).## Contribute
|
||||
## Create a pull request
|
||||
Before or after you create a pull request, sign the [contributor license agreement](http://grafana.org/docs/contributing/cla.html).
|
||||
## Contribute
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue.
|
||||
And if you have time clone this repo and submit a pull request and help me make Grafana
|
||||
the kickass metrics & devops dashboard we all dream about!
|
||||
|
26
bower.json
Normal file
26
bower.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "grafana",
|
||||
"version": "2.0.2",
|
||||
"homepage": "https://github.com/grafana/grafana",
|
||||
"authors": [],
|
||||
"license": "Apache 2.0",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"public/vendor/",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": "~2.1.4",
|
||||
"angular": "~1.4.0",
|
||||
"angular-route": "~1.4.0",
|
||||
"angular-mocks": "~1.4.0",
|
||||
"angular-sanitize": "~1.4.0",
|
||||
"angular-native-dragdrop": "~1.1.0",
|
||||
"angular-bindonce": "~0.3.3",
|
||||
"requirejs": "~2.1.18",
|
||||
"requirejs-text": "~2.0.14"
|
||||
}
|
||||
}
|
@ -22,3 +22,4 @@ test:
|
||||
- godep go test -v ./pkg/...
|
||||
# js tests
|
||||
- ./node_modules/grunt-cli/bin/grunt test
|
||||
- npm run coveralls
|
||||
|
@ -7,7 +7,7 @@ app_mode = production
|
||||
|
||||
#################################### Paths ####################################
|
||||
[paths]
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is useD)
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
#
|
||||
data = data
|
||||
#
|
||||
@ -29,6 +29,10 @@ http_port = 3000
|
||||
# The public facing domain name used to access grafana from a browser
|
||||
domain = localhost
|
||||
|
||||
# Redirect to correct domain if host header does not match domain
|
||||
# Prevents DNS rebinding attacks
|
||||
enforce_domain = false
|
||||
|
||||
# The full public facing url
|
||||
root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||
|
||||
@ -62,14 +66,16 @@ path = grafana.db
|
||||
|
||||
#################################### Session ####################################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", default is "memory"
|
||||
# Either "memory", "file", "redis", "mysql", "postgresql", default is "file"
|
||||
provider = file
|
||||
|
||||
# Provider config options
|
||||
# memory: not have any config yet
|
||||
# file: session dir path, is relative to grafana data_path
|
||||
# redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,grafana`
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
|
||||
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
|
||||
# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
|
||||
|
||||
provider_config = sessions
|
||||
|
||||
# Session cookie name
|
||||
@ -108,6 +114,9 @@ login_remember_days = 7
|
||||
cookie_username = grafana_user
|
||||
cookie_remember_name = grafana_remember
|
||||
|
||||
# disable gravatar profile images
|
||||
disable_gravatar = false
|
||||
|
||||
#################################### Users ####################################
|
||||
[users]
|
||||
# disable user signup / registration
|
||||
@ -136,18 +145,21 @@ org_role = Viewer
|
||||
#################################### Github Auth ##########################
|
||||
[auth.github]
|
||||
enabled = false
|
||||
allow_sign_up = false
|
||||
client_id = some_id
|
||||
client_secret = some_secret
|
||||
scopes = user:email
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
api_url = https://api.github.com/user
|
||||
team_ids =
|
||||
allowed_domains =
|
||||
allow_sign_up = false
|
||||
allowed_organizations =
|
||||
|
||||
#################################### Google Auth ##########################
|
||||
[auth.google]
|
||||
enabled = false
|
||||
allow_sign_up = false
|
||||
client_id = some_client_id
|
||||
client_secret = some_client_secret
|
||||
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
|
||||
@ -155,7 +167,32 @@ auth_url = https://accounts.google.com/o/oauth2/auth
|
||||
token_url = https://accounts.google.com/o/oauth2/token
|
||||
api_url = https://www.googleapis.com/oauth2/v1/userinfo
|
||||
allowed_domains =
|
||||
allow_sign_up = false
|
||||
|
||||
#################################### Basic Auth ##########################
|
||||
[auth.basic]
|
||||
enabled = true
|
||||
|
||||
#################################### Auth Proxy ##########################
|
||||
[auth.proxy]
|
||||
enabled = false
|
||||
header_name = X-WEBAUTH-USER
|
||||
header_property = username
|
||||
auto_sign_up = true
|
||||
|
||||
#################################### SMTP / Emailing ##########################
|
||||
[smtp]
|
||||
enabled = false
|
||||
host = localhost:25
|
||||
user =
|
||||
password =
|
||||
cert_file =
|
||||
key_file =
|
||||
skip_verify = false
|
||||
from_address = admin@grafana.localhost
|
||||
|
||||
[emails]
|
||||
welcome_email_on_sign_up = false
|
||||
templates_pattern = emails/*.html
|
||||
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
@ -196,3 +233,10 @@ max_days = 7
|
||||
enabled = false
|
||||
rabbitmq_url = amqp://localhost/
|
||||
exchange = grafana_events
|
||||
|
||||
#################################### Dashboard JSON files ##########################
|
||||
[dashboards.json]
|
||||
enabled = false
|
||||
path = /var/lib/grafana/dashboards
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
#################################### Paths ####################################
|
||||
[paths]
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is useD)
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
#
|
||||
;data = /var/lib/grafana
|
||||
#
|
||||
@ -29,6 +29,10 @@
|
||||
# The public facing domain name used to access grafana from a browser
|
||||
;domain = localhost
|
||||
|
||||
# Redirect to correct domain if host header does not match domain
|
||||
# Prevents DNS rebinding attacks
|
||||
;enforce_domain = false
|
||||
|
||||
# The full public facing url
|
||||
;root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||
|
||||
@ -62,14 +66,15 @@
|
||||
|
||||
#################################### Session ####################################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", default is "memory"
|
||||
# Either "memory", "file", "redis", "mysql", "postgresql", default is "file"
|
||||
;provider = file
|
||||
|
||||
# Provider config options
|
||||
# memory: not have any config yet
|
||||
# file: session dir path, is relative to grafana data_path
|
||||
# redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,grafana`
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
|
||||
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
|
||||
# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
|
||||
;provider_config = sessions
|
||||
|
||||
# Session cookie name
|
||||
@ -108,6 +113,9 @@
|
||||
;cookie_username = grafana_user
|
||||
;cookie_remember_name = grafana_remember
|
||||
|
||||
# disable gravatar profile images
|
||||
;disable_gravatar = false
|
||||
|
||||
#################################### Users ####################################
|
||||
[users]
|
||||
# disable user signup / registration
|
||||
@ -136,26 +144,53 @@
|
||||
#################################### Github Auth ##########################
|
||||
[auth.github]
|
||||
;enabled = false
|
||||
;allow_sign_up = false
|
||||
;client_id = some_id
|
||||
;client_secret = some_secret
|
||||
;scopes = user:email
|
||||
;scopes = user:email,read:org
|
||||
;auth_url = https://github.com/login/oauth/authorize
|
||||
;token_url = https://github.com/login/oauth/access_token
|
||||
;api_url = https://api.github.com/user
|
||||
# Uncomment bellow to only allow specific email domains
|
||||
; allowed_domains = mycompany.com othercompany.com
|
||||
;team_ids =
|
||||
;allowed_domains =
|
||||
;allowed_organizations =
|
||||
|
||||
#################################### Google Auth ##########################
|
||||
[auth.google]
|
||||
;enabled = false
|
||||
;allow_sign_up = false
|
||||
;client_id = some_client_id
|
||||
;client_secret = some_client_secret
|
||||
;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
|
||||
;auth_url = https://accounts.google.com/o/oauth2/auth
|
||||
;token_url = https://accounts.google.com/o/oauth2/token
|
||||
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
|
||||
# Uncomment bellow to only allow specific email domains
|
||||
; allowed_domains = mycompany.com othercompany.com
|
||||
;allowed_domains =
|
||||
|
||||
#################################### Auth Proxy ##########################
|
||||
[auth.proxy]
|
||||
;enabled = false
|
||||
;header_name = X-WEBAUTH-USER
|
||||
;header_property = username
|
||||
;auto_sign_up = true
|
||||
|
||||
#################################### Basic Auth ##########################
|
||||
[auth.basic]
|
||||
;enabled = true
|
||||
|
||||
#################################### SMTP / Emailing ##########################
|
||||
[smtp]
|
||||
;enabled = false
|
||||
;host = localhost:25
|
||||
;user =
|
||||
;password =
|
||||
;cert_file =
|
||||
;key_file =
|
||||
;skip_verify = false
|
||||
;from_address = admin@grafana.localhost
|
||||
|
||||
[emails]
|
||||
;welcome_email_on_sign_up = false
|
||||
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
@ -196,3 +231,11 @@
|
||||
;enabled = false
|
||||
;rabbitmq_url = amqp://localhost/
|
||||
;exchange = grafana_events
|
||||
|
||||
;#################################### Dashboard JSON files ##########################
|
||||
[dashboards.json]
|
||||
;enabled = false
|
||||
;path = /var/lib/grafana/dashboards
|
||||
|
||||
|
||||
|
||||
|
13
docker/blocks/smtp/Dockerfile
Normal file
13
docker/blocks/smtp/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM centos:centos7
|
||||
MAINTAINER Przemyslaw Ozgo <linux@ozgo.info>
|
||||
|
||||
RUN \
|
||||
yum update -y && \
|
||||
yum install -y net-snmp net-snmp-utils && \
|
||||
yum clean all
|
||||
|
||||
COPY bootstrap.sh /tmp/bootstrap.sh
|
||||
|
||||
EXPOSE 161
|
||||
|
||||
ENTRYPOINT ["/tmp/bootstrap.sh"]
|
27
docker/blocks/smtp/bootstrap.sh
Executable file
27
docker/blocks/smtp/bootstrap.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -u
|
||||
|
||||
# User params
|
||||
USER_PARAMS=$@
|
||||
|
||||
# Internal params
|
||||
RUN_CMD="snmpd -f ${USER_PARAMS}"
|
||||
|
||||
#######################################
|
||||
# Echo/log function
|
||||
# Arguments:
|
||||
# String: value to log
|
||||
#######################################
|
||||
log() {
|
||||
if [[ "$@" ]]; then echo "[`date +'%Y-%m-%d %T'`] $@";
|
||||
else echo; fi
|
||||
}
|
||||
|
||||
# Launch
|
||||
log $RUN_CMD
|
||||
$RUN_CMD
|
||||
|
||||
# Exit immidiately in case of any errors or when we have interactive terminal
|
||||
if [[ $? != 0 ]] || test -t 0; then exit $?; fi
|
||||
log
|
4
docker/blocks/smtp/fig
Normal file
4
docker/blocks/smtp/fig
Normal file
@ -0,0 +1,4 @@
|
||||
snmpd:
|
||||
build: blocks/snmpd
|
||||
ports:
|
||||
- "161:161"
|
@ -1,6 +0,0 @@
|
||||
FROM centos:latest
|
||||
|
||||
RUN yum install -y initscripts
|
||||
|
||||
ADD *.rpm /tmp/
|
||||
|
@ -44,7 +44,7 @@ docs-test: docs-build
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
|
||||
|
||||
docs-build:
|
||||
git fetch https://github.com/grafana/grafana.git docs-2.0 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
|
||||
git fetch https://github.com/grafana/grafana.git docs-1.x && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
|
||||
echo "$(GIT_BRANCH)" > GIT_BRANCH
|
||||
echo "$(GITCOMMIT)" > GITCOMMIT
|
||||
docker build -t "$(DOCKER_DOCS_IMAGE)" .
|
||||
|
@ -1 +1 @@
|
||||
2.0.0-beta
|
||||
2.1.0
|
||||
|
@ -45,7 +45,7 @@ pages:
|
||||
|
||||
- ['reference/graph.md', 'Reference', 'Graph Panel']
|
||||
- ['reference/singlestat.md', 'Reference', 'Singlestat Panel']
|
||||
- ['reference/dashlist.md', 'Reference', 'Dashlist Panel']
|
||||
- ['reference/dashlist.md', 'Reference', 'Dashboard list Panel']
|
||||
- ['reference/sharing.md', 'Reference', 'Sharing']
|
||||
- ['reference/annotations.md', 'Reference', 'Annotations']
|
||||
- ['reference/timerange.md', 'Reference', 'Time range controls']
|
||||
@ -60,8 +60,9 @@ pages:
|
||||
- ['datasources/graphite.md', 'Data Sources', 'Graphite']
|
||||
- ['datasources/influxdb.md', 'Data Sources', 'InfluxDB']
|
||||
- ['datasources/opentsdb.md', 'Data Sources', 'OpenTSDB']
|
||||
- ['datasources/kairosdb.md', 'Data Sources', 'KairosDB']
|
||||
|
||||
- ['project/building_from_source.md', 'Project', 'Building from souce']
|
||||
- ['project/building_from_source.md', 'Project', 'Building from source']
|
||||
- ['project/cla.md', 'Project', 'Contributor License Agreement']
|
||||
|
||||
- ['jsearch.md', '**HIDDEN**']
|
||||
|
@ -6,9 +6,9 @@ page_keywords: grafana, graphite, metrics, query, documentation
|
||||
|
||||
# Graphite
|
||||
|
||||
Grafana has an advanced graphite query editor that lets you quickly navigate the metric space, add functions.
|
||||
Change function paramaters and much more. The editor cannot handle all types of queries yet.
|
||||
To switch to a regular text box click the pen icon to the right.
|
||||
Grafana has an advanced Graphite query editor that lets you quickly navigate the metric space, add functions,
|
||||
change function parameters and much more. The editor can handle all types of graphite queries. It can even handle complex nested
|
||||
queries through the use of query references.
|
||||
|
||||
## Adding the data source to Grafana
|
||||
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
|
||||
@ -52,8 +52,21 @@ Some functions like aliasByNode support an optional second argument. To add this
|
||||
|
||||
## Point consolidation
|
||||
|
||||
All graphite metrics are consolidated so that graphite doesn't return more data points than there are pixels in the graph. By default
|
||||
this consolidation is done using `avg` function. You can how graphite consolidates metrics by adding the Graphite consolidateBy function.
|
||||
All Graphite metrics are consolidated so that Graphite doesn't return more data points than there are pixels in the graph. By default
|
||||
this consolidation is done using `avg` function. You can how Graphite consolidates metrics by adding the Graphite consolidateBy function.
|
||||
|
||||
> *Notice* This means that legend summary values (max, min, total) cannot be all correct at the same time. They are calculated
|
||||
> client side by Grafana. And depending on your consolidation function only one or two can be correct at the same time.
|
||||
|
||||
## Templating
|
||||
You can create a template variable in Grafana and have that variable filled with values from any Graphite metric exploration query.
|
||||
You can then use this variable in your Graphite queries, either as part of a metric path or as arguments to functions.
|
||||
|
||||
For example a query like `prod.servers.*` will fill the variable with all possible
|
||||
values that exists in the wildcard position.
|
||||
|
||||
You can also create nested variables that use other variables in their definition. For example
|
||||
`apps.$app.servers.*` uses the variable `$app` in its query definition.
|
||||
|
||||

|
||||
|
||||
|
@ -4,10 +4,11 @@ page_description: InfluxDB query guide
|
||||
page_keywords: grafana, influxdb, metrics, query, documentation
|
||||
---
|
||||
|
||||
|
||||
# InfluxDB
|
||||
|
||||
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x. The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x. InfluxDB 0.9.x data source support is provided on an experimental basis.
|
||||
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x.
|
||||
The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles
|
||||
them as different data sources.
|
||||
|
||||
## Adding the data source to Grafana
|
||||
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
|
||||
@ -31,37 +32,73 @@ Password | Database user's password
|
||||
> *Note* When using Proxy access mode the InfluxDB database, user and password will be hidden from the browser/frontend. When
|
||||
> using direct access mode all users will be able to see the database user & password.
|
||||
|
||||
## InfluxDB 0.9.x query editor
|
||||
## InfluxDB 0.9.x
|
||||
|
||||
This editor & data source is not compatible with InfluxDB 0.8.x, please use the right data source for you InfluxDB version.
|
||||
The InfluxDB 0.9.x editor is currently under development and is not yet fully usable.
|
||||

|
||||
|
||||
## InfluxDB 0.8.x query editor
|
||||
You find the InfluxDB editor in the metrics tab in Graph or Singlestat panel's edit mode. You enter edit mode by clicking the
|
||||
panel title, then edit. The editor allows you to select metrics and tags.
|
||||
|
||||
### Editor tag filters
|
||||
To add a tag filter click the plus icon to the right of the `WHERE` condition. You can remove tag filters by clicking on
|
||||
the tag key and select `--remove tag filter--`.
|
||||
|
||||
### Regex matching
|
||||
You can type in regex patterns for metric names or tag filter values, be sure to wrap the regex pattern in forward slashes (`/`). Grafana
|
||||
will automaticallay adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`).
|
||||
|
||||
### Editor group by
|
||||
To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears.
|
||||
You can remove the group by by clicking on the tag and then select `--remove group by--` from the dropdown.
|
||||
|
||||
### Editor RAW Query
|
||||
You can switch to raw query mode by pressing the pen icon.
|
||||
|
||||
> If you use Raw Query be sure your query at minimum have `WHERE $timeFilter` clause and ends with `order by asc`.
|
||||
> Also please always have a group by time and an aggregation function, otherwise InfluxDB can easily return hundreds of thousands
|
||||
> of data points that will hang the browser.
|
||||
|
||||
### Alias patterns
|
||||
|
||||
- $m = replaced with measurement name
|
||||
- $measurement = replaced with measurement name
|
||||
- $tag_hostname = replaced with the value of the hostname tag
|
||||
- You can also use [[tag_hostname]] pattern replacement syntax
|
||||
|
||||
### Templating
|
||||
You can create a template variable in Grafana and have that variable filled with values from any InfluxDB metric exploration query.
|
||||
You can then use this variable in your InfluxDB metric queries.
|
||||
|
||||
For example you can have a variable that contains all values for tag `hostname` if you specify a query like this
|
||||
in the templating edit view.
|
||||
```sql
|
||||
SHOW TAG VALUES WITH KEY = "hostname"
|
||||
```
|
||||
|
||||
You can also create nested variables. For example if you had another variable, for example `region`. Then you could have
|
||||
the hosts variable only show hosts from the current selected region with a query like this:
|
||||
|
||||
```sql
|
||||
SHOW TAG VALUES WITH KEY = "hostname" WHERE region =~ /$region/
|
||||
```
|
||||
|
||||
> Always you `regex values` or `regex wildcard` for All format or multi select format.
|
||||
|
||||

|
||||
|
||||
### Annotations
|
||||
Annotations allows you to overlay rich event information on top of graphs.
|
||||
|
||||
An example query:
|
||||
|
||||
```SQL
|
||||
SELECT title, description from events WHERE $timeFilter order asc
|
||||
```
|
||||
|
||||
### InfluxDB 0.8.x
|
||||
|
||||

|
||||
|
||||
When you add an InfluxDB query you can specify series name (can be regex), value column and a function. Group by time can be specified or if left blank will be automatically set depending on how long the current time span is. It will translate to a InfluxDB query that looks like this:
|
||||
|
||||
```sql
|
||||
select [[func]]([[column]]) from [[series]] where [[timeFilter]] group by time([[interval]]) order asc
|
||||
```
|
||||
|
||||
To write the complete query yourself click the cog wheel icon to the right and select ``Raw query mode``.
|
||||
|
||||
## InfluxDB 0.9 Filters & Templates queries
|
||||
|
||||
The InfluxDB 0.9 data source does not currently support filters or templates.
|
||||
|
||||
## InfluxDB 0.8 Filters & Templated queries
|
||||
|
||||

|
||||
|
||||
|
||||
Use a distinct influxdb query in the filter query input box:
|
||||
|
||||
```sql
|
||||
select distinct(host) from app.status
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
47
docs/sources/datasources/kairosdb.md
Normal file
47
docs/sources/datasources/kairosdb.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
page_title: KairosDB Guide
|
||||
page_description: KairosDB guide for Grafana
|
||||
page_keywords: grafana, kairosdb, documentation
|
||||
---
|
||||
|
||||
# KairosDB Guide
|
||||
|
||||
## Adding the data source to Grafana
|
||||
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
|
||||
should find a link named `Data Sources`. If this link is missing in the side menu it means that your current
|
||||
user does not have the `Admin` role for the current organization.
|
||||
|
||||
<!--  -->
|
||||
|
||||
Now click the `Add new` link in the top header.
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
|
||||
Default | Default data source means that it will be pre-selected for new panels.
|
||||
Url | The http protocol, ip and port of your kairosdb server (default port is usually 8080)
|
||||
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
|
||||
|
||||
## Query editor
|
||||
Open a graph in edit mode by click the title.
|
||||
|
||||
<!--  -->
|
||||
|
||||
For details on KairosDB metric queries checkout the offical.
|
||||
|
||||
- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html).
|
||||
|
||||
## Templated queries
|
||||
KairosDB Datasource Plugin provides following functions in `Variables values query` field in Templating Editor to query `metric names`, `tag names`, and `tag values` to kairosdb server.
|
||||
|
||||
Name | Description
|
||||
---- | ----
|
||||
`metrics(query)` | Returns a list of metric names. If nothing is given, returns a list of all metric names.
|
||||
`tag_names(query)` | Returns a list of tag names. If nothing is given, returns a list of all tag names.
|
||||
`tag_values(query)` | Returns a list of tag values. If nothing is given, returns a list of all tag values.
|
||||
|
||||
For details of `metric names`, `tag names`, and `tag values`, please refer to the KairosDB documentations.
|
||||
|
||||
- [List Metric Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListMetricNames.html)
|
||||
- [List Tag Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagNames.html)
|
||||
- [List Tag Values - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagValues.html)
|
@ -27,7 +27,19 @@ Open a graph in edit mode by click the title.
|
||||
|
||||

|
||||
|
||||
For details on opentsdb metric queries checkout the offical [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html)
|
||||
### Auto complete suggestions
|
||||
You should get auto complete suggestions for tags and tag values. If you do not you need to enable `tsd.core.meta.enable_realtime_ts` in
|
||||
the OpentSDB server settings. This is required for the OpenTSDB `lookup` api to work.
|
||||
|
||||
## Templating queries
|
||||
|
||||
When using OpenTSDB with a template variable of `query` type you can use following syntax for lookup.
|
||||
|
||||
metrics() // returns metric names
|
||||
tag_names(cpu) // return tag names (i.e. keys) for a specific cpu metric
|
||||
tag_values(cpu, hostname) // return tag values for metric cpu and tag key hostname
|
||||
|
||||
For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html)
|
||||
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ The image above shows you the top header for a dashboard.
|
||||
|
||||
1. Side menubar toggle: This toggles the side menu, allowing you to focus on the data presented in the dashboard. The side menu provides access to features unrelated to a Dashboard such as Users, Organizations, and Data Sources.
|
||||
2. Dashboard dropdown: This dropdown shows you which Dashboard you are currently viewing, and allows you to easily switch to a new Dashboard. From here you can also create a new Dashboard, Import existing Dashboards, and manage Dashboard playlists.
|
||||
3. Star Dashboard: Star (or unstar) the current Dashboar. Starred Dashboards will show up on your own Home Dashboard by default, and are a convenient way to mark Dashboards that you're interested in.
|
||||
3. Star Dashboard: Star (or unstar) the current Dashboard. Starred Dashboards will show up on your own Home Dashboard by default, and are a convenient way to mark Dashboards that you're interested in.
|
||||
4. Share Dashboard: Share the current dashboard by creating a link or create a static Snapshot of it. Make sure the Dashboard is saved before sharing.
|
||||
5. Save dashboard: The current Dashboard will be saved with the current Dashboard name.
|
||||
6. Settings: Manage Dashboard settings and features such as Templating and Annotations.
|
||||
@ -28,7 +28,7 @@ The image above shows you the top header for a dashboard.
|
||||
Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows.
|
||||
By adjusting the display properties of Panels and Rows, you can customize the perfect Dashboard for your exact needs.
|
||||
Each panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, and KairosDB).
|
||||
This allows you to create a single dashboard that unifies the data across your organization. Panels use the time range specificed
|
||||
This allows you to create a single dashboard that unifies the data across your organization. Panels use the time range specified
|
||||
in the main Time Picker in the upper right, but they can also have relative time overrides.
|
||||
|
||||
<img src="/img/v2/dashboard_annotated.png" class="no-shadow">
|
||||
@ -40,6 +40,14 @@ in the main Time Picker in the upper right, but they can also have relative time
|
||||
5. Dashboard panel. You edit panels by clicking the panel title.
|
||||
6. Graph legend. You can change series colors, y-axis and series visibility directly from the legend.
|
||||
|
||||
## Adding & Editing Graphs and Panels
|
||||
|
||||

|
||||
|
||||
1. You add panels via row menu. The row menu is the green icon to the left of each row.
|
||||
2. To edit the graph you click on the graph title to open the panel menu, then `Edit`.
|
||||
3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source.
|
||||
|
||||
## Drag-and-Drop panels
|
||||
|
||||
You can Drag-and-Drop Panels within and between Rows. Click and hold the Panel title, and drag it to its new location.
|
||||
|
@ -15,10 +15,9 @@ no_toc: true
|
||||
<div class="columns medium-6">
|
||||
<h3>Episode 2 - Templated Graphite Queries</h3>
|
||||
<div class="video-container">
|
||||
<iframe height="315" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe height="215" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -34,7 +33,6 @@ no_toc: true
|
||||
<iframe height="215" src="https://www.youtube.com/embed/JY22EBOR9hQ?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -50,7 +48,6 @@ no_toc: true
|
||||
<iframe height="215" src="https://www.youtube.com/embed/9ZCMVNxUf6s?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -4,13 +4,13 @@ page_keywords: grafana, introduction, documentation, about
|
||||
|
||||
# About Grafana
|
||||
|
||||
Grafana is a leading open source applications for visualizing large-scale measurement data.
|
||||
Grafana is a leading open source application for visualizing large-scale measurement data.
|
||||
|
||||
It provides a powerful and elegant way to create, share, and explore data and dashboards from your disparate metric databases, either with your team or the world.
|
||||
|
||||
Grafana is most commonly used for Internet infrastructure and application analytics, but many use it in other domains including industrial sensors, home automation, weather, and process control.
|
||||
|
||||
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for KairosDB, and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
|
||||
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for [KairosDB](https://github.com/kairosdb/kairosdb), and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
|
||||
|
||||
Version 2.0 was released in April 2015: Grafana now ships with its own backend server that brings [many changes and features](../guides/whats-new-in-v2/).
|
||||
|
||||
|
@ -6,173 +6,243 @@ page_keywords: grafana, configuration, documentation
|
||||
|
||||
# Configuration
|
||||
|
||||
The Grafana backend has a number of configuration options that can be specified in a `.ini` config file
|
||||
or specified using `ENV` variables.
|
||||
The Grafana back-end has a number of configuration options that can be
|
||||
specified in a `.ini` configuration file or specified using environment variables.
|
||||
|
||||
## Config file locations
|
||||
|
||||
- Default configuration from `$WORKING_DIR/conf/defaults.ini`
|
||||
- Custom configuration from `$WORKING_DIR/conf/custom.ini`
|
||||
- The custom config file path can be overriden using the `--config` parameter
|
||||
- The custom configuration file path can be overridden using the `--config` parameter
|
||||
|
||||
> **Note.** If you have installed grafana using the `deb` or `rpm` packages, then your configuration file is located
|
||||
> at `/etc/grafana/grafana.ini`. This path is specified in the grafana init.d script using `--config` file
|
||||
> parameter.
|
||||
> **Note.** If you have installed Grafana using the `deb` or `rpm`
|
||||
> packages, then your configuration file is located at
|
||||
> `/etc/grafana/grafana.ini`. This path is specified in the Grafana
|
||||
> init.d script using `-config` file parameter.
|
||||
|
||||
## Using ENV variables
|
||||
All options in the config file (listed below) can be overriden using ENV variables using the syntax:
|
||||
## Using environment variables
|
||||
|
||||
All options in the configuration file (listed below) can be overridden
|
||||
using environment variables using the syntax:
|
||||
|
||||
GF_<SectionName>_<KeyName>
|
||||
|
||||
Where the section name is the text within the brackets. Everything should be upper case.
|
||||
|
||||
Example, given this config setting:
|
||||
Where the section name is the text within the brackets. Everything
|
||||
should be upper case, `.` should be replaced by `_`. For example, given these configuration settings:
|
||||
|
||||
[security]
|
||||
admin_user = admin
|
||||
|
||||
[auth.google]
|
||||
client_secret = 0ldS3cretKey
|
||||
|
||||
|
||||
Then you can override that using:
|
||||
|
||||
export GF_SECURITY_ADMIN_USER=true
|
||||
export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey
|
||||
|
||||
<hr>
|
||||
|
||||
## [paths]
|
||||
|
||||
### data
|
||||
Path to where grafana can store the sqlite3 database (if used), file based sessions (if used), and other data.
|
||||
This path is usually specified via command line in the init.d script or the systemd service file.
|
||||
|
||||
Path to where Grafana stores the sqlite3 database (if used), file based
|
||||
sessions (if used), and other data. This path is usually specified via
|
||||
command line in the init.d script or the systemd service file.
|
||||
|
||||
### logs
|
||||
Path to where grafana can store logs. This path is usually specified via command line in the init.d script or the systemd service file.
|
||||
It can be overriden in the config file or in the default environment variable file.
|
||||
|
||||
Path to where Grafana will store logs. This path is usually specified via
|
||||
command line in the init.d script or the systemd service file. It can
|
||||
be overridden in the configuration file or in the default environment variable
|
||||
file.
|
||||
|
||||
## [server]
|
||||
|
||||
### http_addr
|
||||
The ip address to bind to, if empty will bind to all interfaces
|
||||
|
||||
The IP address to bind to, if empty will bind to all interfaces
|
||||
|
||||
### http_port
|
||||
The port to bind to, defaults to `3000`. To use port 80 you need to either give the grafana binary permission for example:
|
||||
|
||||
```
|
||||
$ sudo setcap 'cap_net_bind_service=+ep' /opt/grafana/current/grafana
|
||||
```
|
||||
The port to bind to, defaults to `3000`. To use port 80 you need to
|
||||
either give the Grafana binary permission for example:
|
||||
|
||||
Or redirect port 80 to the grafana port using:
|
||||
```
|
||||
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
|
||||
```
|
||||
$ sudo setcap 'cap_net_bind_service=+ep' /opt/grafana/current/grafana
|
||||
|
||||
Another way is put nginx or apache infront of Grafana and have them proxy requests to Grafana.
|
||||
Or redirect port 80 to the Grafana port using:
|
||||
|
||||
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
|
||||
|
||||
Another way is put a webserver like Nginx or Apache in front of Grafana and have them proxy requests to Grafana.
|
||||
|
||||
### protocol
|
||||
|
||||
`http` or `https`
|
||||
|
||||
### domain
|
||||
This setting is only used in as a part of the root_url setting (see below). Important if you
|
||||
use github or google oauth.
|
||||
|
||||
This setting is only used in as a part of the `root_url` setting (see below). Important if you
|
||||
use GitHub or Google OAuth.
|
||||
|
||||
### enforce_domain
|
||||
|
||||
Redirect to correct domain if host header does not match domain.
|
||||
Prevents DNS rebinding attacks. Default is false.
|
||||
|
||||
### root_url
|
||||
This is the full url used to access grafana from a web browser. This is important if you use
|
||||
google or github oauth authentication (for the callback url to be correct).
|
||||
|
||||
> **Note** This setting is also important if you have a reverse proxy infront of Grafana
|
||||
> that exposes grafana through a subpath. In that case add the subpath to the end of this url setting.
|
||||
This is the full URL used to access Grafana from a web browser. This is
|
||||
important if you use Google or GitHub OAuth authentication (for the
|
||||
callback URL to be correct).
|
||||
|
||||
> **Note** This setting is also important if you have a reverse proxy
|
||||
> in front of Grafana that exposes it through a subpath. In that
|
||||
> case add the subpath to the end of this URL setting.
|
||||
|
||||
### static_root_path
|
||||
The path to the directory where the frontend files (html & js & css). Default to `public` which is
|
||||
why the Grafana binary needs to be executed with working directory set to the installation path.
|
||||
|
||||
The path to the directory where the front end files (HTML, JS, and CSS
|
||||
files). Default to `public` which is why the Grafana binary needs to be
|
||||
executed with working directory set to the installation path.
|
||||
|
||||
### cert_file
|
||||
Path to cert file (if protocol is https)
|
||||
|
||||
Path to the certificate file (if `protocol` is set to `https`).
|
||||
|
||||
### cert_key
|
||||
Path to cert key file (if protocol is https)
|
||||
|
||||
Path to the certificate key file (if `protocol` is set to `https`).
|
||||
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
|
||||
## [database]
|
||||
|
||||
Grafana needs a database to store users and dashboards (and other things). By default it is configured to
|
||||
use `sqlite3` which is an embedded database (included in the main Grafana binary).
|
||||
Grafana needs a database to store users and dashboards (and other
|
||||
things). By default it is configured to use `sqlite3` which is an
|
||||
embedded database (included in the main Grafana binary).
|
||||
|
||||
### type
|
||||
|
||||
Either `mysql`, `postgres` or `sqlite3`, it's your choice.
|
||||
|
||||
### path
|
||||
Only applicable for `sqlite3` database. The file path where the database will be stored.
|
||||
|
||||
Only applicable for `sqlite3` database. The file path where the database
|
||||
will be stored.
|
||||
|
||||
### host
|
||||
Only applicable to mysql or postgres. Include ip/hostname & port.
|
||||
Example for mysql same host as Grafana: `host = 127.0.0.1:3306`
|
||||
|
||||
Only applicable to MySQL or Postgres. Includes IP or hostname and port.
|
||||
For example, for MySQL running on the same host as Grafana: `host =
|
||||
127.0.0.1:3306`
|
||||
|
||||
### name
|
||||
The name of the grafana database. Leave it set to `grafana` or some other name.
|
||||
|
||||
The name of the Grafana database. Leave it set to `grafana` or some
|
||||
other name.
|
||||
|
||||
### user
|
||||
|
||||
The database user (not applicable for `sqlite3`).
|
||||
|
||||
### password
|
||||
|
||||
The database user's password (not applicable for `sqlite3`).
|
||||
|
||||
### ssl_mode
|
||||
For `postgres` only, either "disable", "require" or "verify-full".
|
||||
|
||||
For `postgres` only, either `disable`, `require` or `verify-full`.
|
||||
|
||||
<hr>
|
||||
|
||||
## [security]
|
||||
|
||||
### admin_user
|
||||
The name of the default grafana admin user (who has full permissions). Defaults to `admin`.
|
||||
|
||||
The name of the default Grafana admin user (who has full permissions).
|
||||
Defaults to `admin`.
|
||||
|
||||
### admin_password
|
||||
The password of the default grafana admin. Defaults to `admin`.
|
||||
|
||||
The password of the default Grafana admin. Defaults to `admin`.
|
||||
|
||||
### login_remember_days
|
||||
|
||||
The number of days the keep me logged in / remember me cookie lasts.
|
||||
|
||||
### secret_key
|
||||
|
||||
Used for signing keep me logged in / remember me cookies.
|
||||
|
||||
### disable_gravatar
|
||||
|
||||
Set to `true` to disable the use of Gravatar for user profile images.
|
||||
Default is `false`.
|
||||
|
||||
<hr>
|
||||
## [user]
|
||||
|
||||
## [users]
|
||||
|
||||
### allow_sign_up
|
||||
Set to `false` to prohibit users from being able to sign up / create user accounts. Defaults to `true`.
|
||||
The admin can still create users from the [Grafana Admin Pages](../reference/admin.md)
|
||||
|
||||
Set to `false` to prohibit users from being able to sign up / create
|
||||
user accounts. Defaults to `true`. The admin user can still create
|
||||
users from the [Grafana Admin Pages](../reference/admin.md)
|
||||
|
||||
### allow_org_create
|
||||
Set to `false` to prohibit users from creating new organizations. Defaults to `true`.
|
||||
|
||||
Set to `false` to prohibit users from creating new organizations.
|
||||
Defaults to `true`.
|
||||
|
||||
### auto_assign_org
|
||||
Set to `true` to automatically add new users to the main organization (id 1). When set to `false`,
|
||||
new users will automatically cause a new organization to be created for that new user.
|
||||
|
||||
Set to `true` to automatically add new users to the main organization
|
||||
(id 1). When set to `false`, new users will automatically cause a new
|
||||
organization to be created for that new user.
|
||||
|
||||
### auto_assign_org_role
|
||||
The role new users will be assigned for the main organization (if the above setting is set to true).
|
||||
Defaults to `Viewer`, other valid options are `Admin` and `Editor`.
|
||||
|
||||
The role new users will be assigned for the main organization (if the
|
||||
above setting is set to true). Defaults to `Viewer`, other valid
|
||||
options are `Admin` and `Editor`.
|
||||
|
||||
<hr>
|
||||
|
||||
## [auth.anonymous]
|
||||
|
||||
### enabled
|
||||
Set to `true` to enable anonymous access. Defaults to `false`
|
||||
### org_name
|
||||
Set the organization name that should be used for anonymous users. If you change your organization name
|
||||
in the Grafana UI this setting needs to be updated to match the new name.
|
||||
### org_role
|
||||
Specify role for anonymous users. Defaults to `Viewer`, other valid options are `Editor` and `Admin`.
|
||||
|
||||
Set to `true` to enable anonymous access. Defaults to `false`
|
||||
|
||||
### org_name
|
||||
|
||||
Set the organization name that should be used for anonymous users. If
|
||||
you change your organization name in the Grafana UI this setting needs
|
||||
to be updated to match the new name.
|
||||
|
||||
### org_role
|
||||
|
||||
Specify role for anonymous users. Defaults to `Viewer`, other valid
|
||||
options are `Editor` and `Admin`.
|
||||
|
||||
## [auth.github]
|
||||
You need to create a github application (you find this under the github profile page). When
|
||||
you create the application you will need to specify a callback URL. Specify this as callback:
|
||||
|
||||
You need to create a GitHub application (you find this under the GitHub
|
||||
profile page). When you create the application you will need to specify
|
||||
a callback URL. Specify this as callback:
|
||||
|
||||
http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/github
|
||||
|
||||
This callback url must match the full http address that you use in your browser to access grafana, but
|
||||
with the prefix path of `/login/github`. When the github application is created you will get a
|
||||
Client ID and a Client Secret. Specify these in the grafana config file. Example:
|
||||
This callback URL must match the full HTTP address that you use in your
|
||||
browser to access Grafana, but with the prefix path of `/login/github`.
|
||||
When the GitHub application is created you will get a Client ID and a
|
||||
Client Secret. Specify these in the Grafana configuration file. For
|
||||
example:
|
||||
|
||||
[auth.github]
|
||||
enabled = true
|
||||
@ -182,22 +252,47 @@ Client ID and a Client Secret. Specify these in the grafana config file. Example
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
allow_sign_up = false
|
||||
team_ids =
|
||||
|
||||
Restart the grafana backend. You should now see a github login button on the login page. You can
|
||||
now login or signup with your github accounts.
|
||||
Restart the Grafana back-end. You should now see a GitHub login button
|
||||
on the login page. You can now login or sign up with your GitHub
|
||||
accounts.
|
||||
|
||||
You may allow users to sign-up via github auth by setting allow_sign_up to true. When this option is
|
||||
set to true, any user successfully authenticating via github auth will be automatically signed up.
|
||||
You may allow users to sign-up via GitHub authentication by setting the
|
||||
`allow_sign_up` option to `true`. When this option is set to `true`, any
|
||||
user successfully authenticating via GitHub authentication will be
|
||||
automatically signed up.
|
||||
|
||||
### team_ids
|
||||
|
||||
Require an active team membership for at least one of the given teams on
|
||||
GitHub. If the authenticated user isn't a member of at least one the
|
||||
teams they will not be able to register or authenticate with your
|
||||
Grafana instance. For example:
|
||||
|
||||
[auth.github]
|
||||
enabled = true
|
||||
client_id = YOUR_GITHUB_APP_CLIENT_ID
|
||||
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
|
||||
scopes = user:email
|
||||
team_ids = 150,300
|
||||
auth_url = https://github.com/login/oauth/authorize
|
||||
token_url = https://github.com/login/oauth/access_token
|
||||
allow_sign_up = false
|
||||
|
||||
## [auth.google]
|
||||
You need to create a google project. You can do this in the [Google Developer Console](https://console.developers.google.com/project).
|
||||
When you create the project you will need to specify a callback URL. Specify this as callback:
|
||||
|
||||
You need to create a Google project. You can do this in the [Google
|
||||
Developer Console](https://console.developers.google.com/project). When
|
||||
you create the project you will need to specify a callback URL. Specify
|
||||
this as callback:
|
||||
|
||||
http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/google
|
||||
|
||||
This callback url must match the full http address that you use in your browser to access grafana, but
|
||||
with the prefix path of `/login/google`. When the google project is created you will get a
|
||||
Client ID and a Client Secret. Specify these in the grafana config file. Example:
|
||||
This callback URL must match the full HTTP address that you use in your
|
||||
browser to access Grafana, but with the prefix path of `/login/google`.
|
||||
When the Google project is created you will get a Client ID and a Client
|
||||
Secret. Specify these in the Grafana configuration file. For example:
|
||||
|
||||
[auth.google]
|
||||
enabled = true
|
||||
@ -206,28 +301,38 @@ Client ID and a Client Secret. Specify these in the grafana config file. Example
|
||||
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
|
||||
auth_url = https://accounts.google.com/o/oauth2/auth
|
||||
token_url = https://accounts.google.com/o/oauth2/token
|
||||
allowed_domains = mycompany.com
|
||||
allowed_domains = mycompany.com mycompany.org
|
||||
allow_sign_up = false
|
||||
|
||||
Restart the grafana backend. You should now see a google login button on the login page. You can
|
||||
now login or signup with your google accounts. `allowed_domains` option is optional.
|
||||
Restart the Grafana back-end. You should now see a Google login button
|
||||
on the login page. You can now login or sign up with your Google
|
||||
accounts. The `allowed_domains` option is optional, and domains were separated by space.
|
||||
|
||||
You may allow users to sign-up via google auth by setting allow_sign_up to true. When this option is
|
||||
set to true, any user successfully authenticating via google auth will be automatically signed up.
|
||||
You may allow users to sign-up via Google authentication by setting the
|
||||
`allow_sign_up` option to `true`. When this option is set to `true`, any
|
||||
user successfully authenticating via Google authentication will be
|
||||
automatically signed up.
|
||||
|
||||
<hr>
|
||||
|
||||
## [session]
|
||||
|
||||
### provider
|
||||
Valid values are "memory", "file", "mysql", 'postgres'. Default is "memory".
|
||||
|
||||
Valid values are `memory`, `file`, `mysql`, `postgres`. Default is `file`.
|
||||
|
||||
### provider_config
|
||||
This option should be configured differently depending on what type of session provider you have configured.
|
||||
|
||||
This option should be configured differently depending on what type of
|
||||
session provider you have configured.
|
||||
|
||||
- **file:** session file path, e.g. `data/sessions`
|
||||
- **mysql:** go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
|
||||
- **mysql:** go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
|
||||
- **postgres:** ex: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
|
||||
|
||||
If you use MySQL or Postgres as the session store you need to create the
|
||||
session table manually.
|
||||
|
||||
if you use mysql or postgres as session store you need to create the session table manually.
|
||||
Mysql Example:
|
||||
|
||||
CREATE TABLE `session` (
|
||||
@ -238,24 +343,38 @@ Mysql Example:
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
### cookie_name
|
||||
The name of the grafana session cookie
|
||||
|
||||
The name of the Grafana session cookie.
|
||||
|
||||
### cookie_secure
|
||||
|
||||
Set to true if you host Grafana behind HTTPs only. Defaults to `false`.
|
||||
|
||||
### session_life_time
|
||||
|
||||
How long sessions lasts in seconds. Defaults to `86400` (24 hours).
|
||||
|
||||
## [analytics]
|
||||
|
||||
### reporting_enabled
|
||||
When enabled Grafana will send anonymous usage statistics to stats.grafana.org.
|
||||
No ip addresses are being tracked, only simple counters to track running instances,
|
||||
|
||||
When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`.
|
||||
No IP addresses are being tracked, only simple counters to track running instances,
|
||||
versions, dashboard & error counts. It is very helpful to us, please leave this
|
||||
enabled. Counters are sent every 24 hours. Default value is `true`.
|
||||
|
||||
### google_analytics_ua_id
|
||||
If you want to track Grafana usage via Google analytics specify *your* Univeral Analytics ID
|
||||
here. By defualt this feature is disabled.
|
||||
|
||||
If you want to track Grafana usage via Google analytics specify *your* Universal Analytics ID
|
||||
here. By default this feature is disabled.
|
||||
|
||||
## [dashboards.json]
|
||||
|
||||
If you have a system that automatically builds dashboards as json files you can enable this feature to have the
|
||||
Grafana backend index those json dashboards which will make them appear in regular dashboard search.
|
||||
|
||||
### enabled
|
||||
`true` or `false`. Is disabled by default.
|
||||
|
||||
### path
|
||||
The full path to a directory containing your json dashboards.
|
||||
|
@ -16,80 +16,93 @@ Description | Download
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_2.0.1_amd64.deb
|
||||
$ sudo dpkg -i grafana_2.0.2_amd64.deb
|
||||
|
||||
## APT Repository
|
||||
Add the following line to your `/etc/apt/sources.list`
|
||||
Add the following line to your `/etc/apt/sources.list` file.
|
||||
|
||||
deb https://packagecloud.io/grafana/stable/debian/ wheezy main
|
||||
|
||||
Use the above line even if you are on Ubuntu or another debian version. There is also testing
|
||||
repository if you want beta or release candidates.
|
||||
Use the above line even if you are on Ubuntu or another Debian version.
|
||||
There is also a testing repository if you want beta or release
|
||||
candidates.
|
||||
|
||||
deb https://packagecloud.io/grafana/testing/debian/ wheezy main
|
||||
|
||||
Then add the [Package Cloud](https://packagecloud.io/grafana) key (signs repo metadata).
|
||||
Then add the [Package Cloud](https://packagecloud.io/grafana) key. This
|
||||
allows you to install signed packages.
|
||||
|
||||
$ curl https://packagecloud.io/gpg.key | sudo apt-key add -
|
||||
|
||||
Update apt and install Grafana
|
||||
Update your Apt repositories and install Grafana
|
||||
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install grafana
|
||||
|
||||
On some older versions of Ubuntu and Debian you may need to install `apt-transport-https`,
|
||||
needed to fetch packages over HTTPS.
|
||||
On some older versions of Ubuntu and Debian you may need to install the
|
||||
`apt-transport-https` package which is needed to fetch packages over
|
||||
HTTPS.
|
||||
|
||||
$ sudo apt-get install -y apt-transport-https
|
||||
|
||||
## Package details
|
||||
|
||||
- Installs binary to `/usr/sbin/grafana-server`
|
||||
- Init.d script to `/etc/init.d/grafana-server`
|
||||
- Default file (environment vars) to `/etc/default/grafana-server`
|
||||
- Configuration file to `/etc/grafana/grafana.ini`
|
||||
- Systemd service (if systemd is available) name `grafana-server.service`
|
||||
- The default configuration specifies log file at `/var/log/grafana/grafana.log`
|
||||
- The default configuration specifies sqlite3 db at `/var/lib/grafana/grafana.db`
|
||||
- Installs Init.d script to `/etc/init.d/grafana-server`
|
||||
- Creates default file (environment vars) to `/etc/default/grafana-server`
|
||||
- Installs configuration file to `/etc/grafana/grafana.ini`
|
||||
- Installs systemd service (if systemd is available) name `grafana-server.service`
|
||||
- The default configuration sets the log file at `/var/log/grafana/grafana.log`
|
||||
- The default configuration specifies an sqlite3 db at `/var/lib/grafana/grafana.db`
|
||||
|
||||
## Start the server (init.d service)
|
||||
|
||||
- Start grafana by `sudo service grafana-server start`
|
||||
- This will start the grafana-server process as the `grafana` user (created during package install)
|
||||
- Default http port is `3000`, and default user is admin/admin
|
||||
You can start Grafana by running:
|
||||
|
||||
To configure Grafana server to start at boot time:
|
||||
$ sudo service grafana-server start
|
||||
|
||||
This will start the `grafana-server` process as the `grafana` user,
|
||||
which was created during the package installation. The default HTTP port
|
||||
is `3000` and default user and group is `admin`.
|
||||
|
||||
To configure the Grafana server to start at boot time:
|
||||
|
||||
$ sudo update-rc.d grafana-server defaults 95 10
|
||||
|
||||
## Start the server (via systemd)
|
||||
|
||||
To start the service using systemd.
|
||||
|
||||
$ systemctl daemon-reload
|
||||
$ systemctl start grafana-server
|
||||
$ systemctl status grafana-server
|
||||
|
||||
Enable the systemd service (so grafana starts at boot)
|
||||
Enable the systemd service so that Grafana starts at boot.
|
||||
|
||||
sudo systemctl enable grafana-server.service
|
||||
|
||||
## Environment file
|
||||
|
||||
The systemd service file and init.d script both use the file located at `/etc/default/grafana-server` for
|
||||
environment variables used when starting the backend. Here you can override log directory, data directory and other
|
||||
variables.
|
||||
The systemd service file and init.d script both use the file located at
|
||||
`/etc/default/grafana-server` for environment variables used when
|
||||
starting the back-end. Here you can override log directory, data
|
||||
directory and other variables.
|
||||
|
||||
### Logging
|
||||
|
||||
By default grafana will log to `/var/log/grafana`
|
||||
By default Grafana will log to `/var/log/grafana`
|
||||
|
||||
### Database
|
||||
|
||||
The default configuration specifies a sqlite3 database located at `/var/lib/grafana/grafana.db`. Please backup
|
||||
this database before upgrades. You can also use mysql or postgres as the Grafana database.
|
||||
The default configuration specifies a sqlite3 database located at
|
||||
`/var/lib/grafana/grafana.db`. Please backup this database before
|
||||
upgrades. You can also use MySQL or Postgres as the Grafana database.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration file is located at `/etc/grafana/grafana.ini`. Go the [Configuration](configuration) page for details
|
||||
on all those options.
|
||||
The configuration file is located at `/etc/grafana/grafana.ini`. Go the
|
||||
[Configuration](/installation/configuration) page for details on all
|
||||
those options.
|
||||
|
||||
### Adding data sources
|
||||
|
||||
@ -99,12 +112,18 @@ on all those options.
|
||||
|
||||
## Installing from binary tar file
|
||||
|
||||
Start by [downloading](http://grafana.org/download/builds) the latest `.tar.gz` file and extract it.
|
||||
This will extract into a folder named after the version you downloaded. This folder contains all files required to run grafana.
|
||||
There are no init scripts or install scripts in this package.
|
||||
Start by [downloading](http://grafana.org/download/builds) the latest
|
||||
`.tar.gz` file and extract it. This will extract into a folder named
|
||||
after the version you downloaded. This folder contains all files
|
||||
required to run Grafana. There are no init scripts or install scripts
|
||||
in this package.
|
||||
|
||||
To configure grafana add a config file named `custom.ini` to the `conf` folder and override any of the settings defined in
|
||||
`conf/defaults.ini`. Start grafana by excecuting `./grafana web`. The grafana binary needs the working directory
|
||||
to be the root install dir (where the binary is and the public folder is located).
|
||||
To configure Grafana add a configuration file named `custom.ini` to the
|
||||
`conf` folder and override any of the settings defined in
|
||||
`conf/defaults.ini`.
|
||||
|
||||
Start Grafana by executing `./grafana web`. The `grafana` binary needs
|
||||
the working directory to be the root install directory (where the binary
|
||||
and the `public` folder is located).
|
||||
|
||||
|
||||
|
@ -8,27 +8,31 @@ page_keywords: grafana, installation, docker, container, guide
|
||||
|
||||
## Install from offical docker image
|
||||
|
||||
Grafana has an offical docker container.
|
||||
Grafana has an official Docker container.
|
||||
|
||||
$ docker run -i -p 3000:3000 grafana/grafana
|
||||
|
||||
All grafana configuration settings can be defined using ENVIRONMENT variables, this is especially useful when using the
|
||||
above container.
|
||||
All Grafana configuration settings can be defined using environment
|
||||
variables, this is especially useful when using the above container.
|
||||
|
||||
## Docker volumes & ENV config
|
||||
|
||||
The docker container exposes two volumes, the sqlite3 database in the folder `/var/lib/grafana` and
|
||||
configuration files is in `/etc/grafana/` folder. You can map these volumes to host folders when you start the container:
|
||||
The Docker container exposes two volumes, the sqlite3 database in the
|
||||
folder `/var/lib/grafana` and configuration files is in `/etc/grafana/`
|
||||
folder. You can map these volumes to host folders when you start the
|
||||
container:
|
||||
|
||||
$ docker run -d -p 3000:3000 \
|
||||
-v /var/lib/grafana:/var/lib/grafana \
|
||||
-e "GF_SECURITY_ADMIN_PASSWORD=secret \
|
||||
-e "GF_SECURITY_ADMIN_PASSWORD=secret" \
|
||||
grafana/grafana:develop
|
||||
|
||||
In the above example I map the data folder and set a config option via an `ENV` variable.
|
||||
In the above example I map the data folder and sets a configuration option via
|
||||
an `ENV` instruction.
|
||||
|
||||
## Configuration
|
||||
|
||||
The backend web server has a number of configuration options. Go the [Configuration](configuration) page for details
|
||||
on all those options.
|
||||
The back-end web server has a number of configuration options. Go the
|
||||
[Configuration](../installation/configuration.md) page for details on all
|
||||
those options.
|
||||
|
||||
|
@ -6,9 +6,12 @@ page_keywords: grafana, installation, documentation
|
||||
|
||||
# Installation
|
||||
|
||||
Grafana is easily installed via a Debian/Ubuntu package (.deb), via Redhat/Centos package (.rpm) or manually via
|
||||
a tar that contains all required files and binaries. If you can't find a package or binary for your platform you might be able
|
||||
to build one your self, read [build from source](../project/building_from_source) instructions for more information.
|
||||
Grafana is easily installed via a Debian/Ubuntu package (.deb), via
|
||||
Redhat/Centos package (.rpm) or manually via a tarball that contains all
|
||||
required files and binaries. If you can't find a package or binary for
|
||||
your platform you might be able to build one your self, read the [build
|
||||
from source](../project/building_from_source) instructions for more
|
||||
information.
|
||||
|
||||
- [Installing on Debian / Ubuntu](debian.md)
|
||||
- [Installing on RPM-based Linux (CentOS, Fedora, OpenSuse, RedHat)](rpm.md)
|
||||
@ -20,8 +23,9 @@ to build one your self, read [build from source](../project/building_from_source
|
||||
|
||||
## Configuration
|
||||
|
||||
The backend web server has a number of configuration options. Go the [Configuration](configuration) page for details
|
||||
on all those options.
|
||||
The back-end web server has a number of configuration options. Go the
|
||||
[Configuration](/installation/configuration) page for details on all
|
||||
those options.
|
||||
|
||||
## Adding data sources
|
||||
|
||||
|
@ -6,7 +6,8 @@ page_keywords: grafana, installation, mac, osx, guide
|
||||
|
||||
# Installing on Mac
|
||||
|
||||
There are currently no binary build for Mac. But read the [build from source](../project/building_from_source)
|
||||
page for instructions on how to build it yourself.
|
||||
There is currently no binary build for Mac. But read the [build from
|
||||
source](/project/building_from_source) page for instructions on how to
|
||||
build it yourself.
|
||||
|
||||
|
||||
|
@ -6,53 +6,81 @@ page_keywords: grafana, installation, migration, documentation
|
||||
|
||||
# Migrating from v1.x to v2.x
|
||||
|
||||
Grafana 2.0 represents a major update to Grafana. It brings new capabilities, many of which are enabled by its new backend server and integrated database.
|
||||
Grafana 2.0 represents a major update to Grafana. It brings new
|
||||
capabilities, many of which are enabled by its new back-end server and
|
||||
integrated database.
|
||||
|
||||
The new backend lays a solid foundation that we hope to build on over the coming months. For the 2.0 release, it enables authentication as well as server-side sharing and rendering.
|
||||
The new back-end lays a solid foundation that we hope to build on over
|
||||
the coming months. For the 2.0 release, it enables authentication as
|
||||
well as server-side sharing and rendering.
|
||||
|
||||
We've attempted to provide a smooth migration path for V1.9 users to migrate to Grafana 2.0.
|
||||
We've attempted to provide a smooth migration path for v1.9 users to
|
||||
migrate to Grafana 2.0.
|
||||
|
||||
## Adding Data sources
|
||||
|
||||
The config.js file has been deprecated. Data sources are now managed via the UI or [HTTP API](../reference/http_api.md). Manage your organizations data sources by clicking on the `Data Sources` menu on the side menu (which can be toggled via the Grafana icon in the upper left of your browser).
|
||||
The `config.js` file has been deprecated. Data sources are now managed via
|
||||
the UI or [HTTP API](../reference/http_api.md). Manage your
|
||||
organizations data sources by clicking on the `Data Sources` menu on the
|
||||
side menu (which can be toggled via the Grafana icon in the upper left
|
||||
of your browser).
|
||||
|
||||
From here, you can add any Graphite, InfluxDB, elasticsearch, and OpenTSDB datasources that you were using with Grafana 1.x. Grafana 2.0 can be configured to communicate with your datasource using a backend mode which can eliminate many CORS-related issues, as well as provide more secure authentication to your datasources.
|
||||
From here, you can add any Graphite, InfluxDB, elasticsearch, and
|
||||
OpenTSDB data sources that you were using with Grafana 1.x. Grafana 2.0
|
||||
can be configured to communicate with your data source using a back-end
|
||||
mode which can eliminate many CORS-related issues, as well as provide
|
||||
more secure authentication to your data sources.
|
||||
|
||||
> *Note* When you add your data sources please name them exacly as you named them in config.js in Grafana 1.x. That name is referenced by panels
|
||||
> , annotation and template queries. That way when you import your old dashboard they will work without any changes.
|
||||
> *Note* When you add your data sources please name them exactly as you
|
||||
> named them in `config.js` in Grafana 1.x. That name is referenced by
|
||||
> panels, annotation and template queries. That way when you import
|
||||
> your old dashboard they will work without any changes.
|
||||
|
||||
## Importing your existing dashboards
|
||||
|
||||
Grafana 2.0 now has integrated dashboard storage engine that can be configured to use an internal sqlite database, MySQL, or Postgres. This eliminates the need to use Elasticsearch for dashboard storage for Graphite users. Grafana 2.0 does not support storing dashboards in InfluxDB.
|
||||
Grafana 2.0 now has integrated dashboard storage engine that can be
|
||||
configured to use an internal sqlite3 database, MySQL, or Postgres. This
|
||||
eliminates the need to use Elasticsearch for dashboard storage for
|
||||
Graphite users. Grafana 2.0 does not support storing dashboards in
|
||||
InfluxDB.
|
||||
|
||||
You can seamlessly import your existing dashboards.
|
||||
|
||||
### dashboards from Elasticsearch
|
||||
### Importing dashboards from Elasticsearch
|
||||
|
||||
Start by going to the `Data Sources` view (via the side menu), and make sure your elasticsearch datasource is added. Specify the elasticsearch index name where your existing Grafana v1.x dashboards are stored (default is `grafana-dash`).
|
||||
Start by going to the `Data Sources` view (via the side menu), and make
|
||||
sure your Elasticsearch data source is added. Specify the Elasticsearch
|
||||
index name where your existing Grafana v1.x dashboards are stored
|
||||
(the default is `grafana-dash`).
|
||||
|
||||

|
||||
|
||||
### dashboards from InfluxDB
|
||||
### Importing dashboards from InfluxDB
|
||||
|
||||
Start by going to the `Data Sources` view (via the side menu), and make sure your InfluxDB datasource is added. Specify the database name where your Grafana v1.x dashboards are stored, default is `grafana`.
|
||||
Start by going to the `Data Sources` view (via the side menu), and make
|
||||
sure your InfluxDB data source is added. Specify the database name where
|
||||
your Grafana v1.x dashboards are stored, the default is `grafana`.
|
||||
|
||||
### Go to Import dashboards view
|
||||
|
||||
Go to the `Dashboards` view and click on the dashboards search dropdown. Click the `Import` button at the bottom of the search dropdown.
|
||||
Go to the `Dashboards` view and click on the dashboards search drop
|
||||
down. Click the `Import` button at the bottom of the search drop down.
|
||||
|
||||

|
||||
|
||||
### Import view
|
||||
|
||||
In the Import view you find the section `Migrate dashboards`. Pick the datasource you added (from elasticsearch or InfluxDB),
|
||||
and click the `Import` button.
|
||||
In the Import view you find the section `Migrate dashboards`. Pick the
|
||||
data source you added (from Elasticsearch or InfluxDB), and click the
|
||||
`Import` button.
|
||||
|
||||

|
||||
|
||||
Your dashboards should be automatically imported into the Grafana 2.0 backend.
|
||||
Your dashboards should be automatically imported into the Grafana 2.0
|
||||
back-end.
|
||||
|
||||
Dashboards will no longer be stored in your previous elasticsearch or InfluxDB databases.
|
||||
Dashboards will no longer be stored in your previous Elasticsearch or
|
||||
InfluxDB databases.
|
||||
|
||||
### Invite your team
|
||||
|
||||
|
@ -8,9 +8,15 @@ page_keywords: grafana, performance, documentation
|
||||
|
||||
## Graphite
|
||||
|
||||
Graphite 0.9.13 adds a much needed feature to the json rendering API that is very important for Grafana. If you are experiance slow
|
||||
load & rendering times for large time ranges then it is most likely caused by running Graphite 0.9.12 or lower. The latest version
|
||||
of Graphite adds a maxDataPoints parameter to the json render API, without this feature Graphite can return hundreds of thousands of data points per graph, which
|
||||
can hang your browser. Be sure to upgrade to [0.9.13](http://graphite.readthedocs.org/en/latest/releases/0_9_13.html).
|
||||
Graphite 0.9.13 adds a much needed feature to the JSON rendering API
|
||||
that is very important for Grafana. If you are experiencing slow load &
|
||||
rendering times for large time ranges then it is most likely caused by
|
||||
running Graphite 0.9.12 or lower.
|
||||
|
||||
The latest version of Graphite adds a `maxDataPoints` parameter to the
|
||||
JSON render API, without this feature Graphite can return hundreds of
|
||||
thousands of data points per graph, which can hang your browser. Be sure
|
||||
to upgrade to
|
||||
[0.9.13](http://graphite.readthedocs.org/en/latest/releases/0_9_13.html).
|
||||
|
||||
|
||||
|
@ -6,8 +6,9 @@ page_keywords: grafana, provisioning, documentation
|
||||
|
||||
# Provisioning
|
||||
|
||||
Here are links for how to install Grafana (and some include graphite or influxdb as well) via a provisioning
|
||||
system. These are not maintained by any core Grafana team member and might be out of date.
|
||||
Here are links for how to install Grafana (and some include Graphite or
|
||||
InfluxDB as well) via a provisioning system. These are not maintained by
|
||||
any core Grafana team member and might be out of date.
|
||||
|
||||
## Puppet
|
||||
|
||||
@ -17,6 +18,7 @@ system. These are not maintained by any core Grafana team member and might be ou
|
||||
|
||||
* [github.com/bobrik/ansible-grafana](https://github.com/bobrik/ansible-grafana)
|
||||
* [github.com/bitmazk/ansible-digitalocean-influxdb-grafana](https://github.com/bitmazk/ansible-digitalocean-influxdb-grafana)
|
||||
* [github.com/picotrading/ansible-grafana](https://github.com/picotrading/ansible-grafana)
|
||||
|
||||
## Docker
|
||||
|
||||
|
@ -12,17 +12,18 @@ Description | Download
|
||||
------------ | -------------
|
||||
.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.0.2-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm)
|
||||
|
||||
## Install
|
||||
You can install using yum
|
||||
## Install from package file
|
||||
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm
|
||||
|
||||
Or manually using `rpm`
|
||||
Or install manually using `rpm`.
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-2.0.1-1.x86_64.rpm
|
||||
|
||||
## YUM Repository
|
||||
## Install via YUM Repository
|
||||
|
||||
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
||||
|
||||
@ -36,33 +37,43 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
||||
sslverify=1
|
||||
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
|
||||
|
||||
There is also testing repository if you want beta or release candidates.
|
||||
There is also a testing repository if you want beta or release
|
||||
candidates.
|
||||
|
||||
baseurl=https://packagecloud.io/grafana/testing/el/6/$basearch
|
||||
|
||||
Install Grafana
|
||||
Then install Grafana via the `yum` command.
|
||||
|
||||
$ sudo yum install grafana
|
||||
|
||||
### RPM GPG Key
|
||||
The rpms are signed, you can verify the signature with this [public GPG key](https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana).
|
||||
|
||||
The RPMs are signed, you can verify the signature with this [public GPG
|
||||
key](https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana).
|
||||
|
||||
## Package details
|
||||
|
||||
- Installs binary to `/usr/sbin/grafana-server`
|
||||
- Init.d script to `/etc/init.d/grafana-server`
|
||||
- Default file (environment vars) to `/etc/sysconfig/grafana-server`
|
||||
- Configuration file to `/etc/grafana/grafana.ini`
|
||||
- Systemd service (if systemd is available) name `grafana-server.service`
|
||||
- The default configuration specifies log file at `/var/log/grafana/grafana.log`
|
||||
- The default configuration specifies sqlite3 db at `/var/lib/grafana/grafana.db`
|
||||
- Copies init.d script to `/etc/init.d/grafana-server`
|
||||
- Installs default file (environment vars) to `/etc/sysconfig/grafana-server`
|
||||
- Copies configuration file to `/etc/grafana/grafana.ini`
|
||||
- Installs systemd service (if systemd is available) name `grafana-server.service`
|
||||
- The default configuration uses a log file at `/var/log/grafana/grafana.log`
|
||||
- The default configuration specifies an sqlite3 database at `/var/lib/grafana/grafana.db`
|
||||
|
||||
## Start the server (init.d service)
|
||||
|
||||
- Start grafana by `sudo service grafana-server start`
|
||||
- This will start the grafana-server process as the `grafana` user (created during package install)
|
||||
- Default http port is `3000`, and default user is admin/admin
|
||||
- To configure grafana server to start at boot time: `sudo /sbin/chkconfig --add grafana-server`
|
||||
You can start Grafana by running:
|
||||
|
||||
$ sudo service grafana-server start
|
||||
|
||||
This will start the `grafana-server` process as the `grafana` user,
|
||||
which is created during package installation. The default HTTP port is
|
||||
`3000`, and default user and group is `admin`.
|
||||
|
||||
To configure the Grafana server to start at boot time:
|
||||
|
||||
$ sudo /sbin/chkconfig --add grafana-server
|
||||
|
||||
## Start the server (via systemd)
|
||||
|
||||
@ -70,29 +81,32 @@ The rpms are signed, you can verify the signature with this [public GPG key](htt
|
||||
$ systemctl start grafana-server
|
||||
$ systemctl status grafana-server
|
||||
|
||||
### Enable the systemd service (so grafana starts at boot)
|
||||
### Enable the systemd service to start at boot
|
||||
|
||||
sudo systemctl enable grafana-server.service
|
||||
|
||||
## Environment file
|
||||
|
||||
The systemd service file and init.d script both use the file located at `/etc/sysconfig/grafana-server` for
|
||||
environment variables used when starting the backend. Here you can override log directory, data directory and other
|
||||
variables.
|
||||
The systemd service file and init.d script both use the file located at
|
||||
`/etc/sysconfig/grafana-server` for environment variables used when
|
||||
starting the back-end. Here you can override log directory, data
|
||||
directory and other variables.
|
||||
|
||||
### Logging
|
||||
|
||||
By default grafana will log to `/var/log/grafana`
|
||||
By default Grafana will log to `/var/log/grafana`
|
||||
|
||||
### Database
|
||||
|
||||
The default configuration specifies a sqlite3 database located at `/var/lib/grafana/grafana.db`. Please backup
|
||||
this database before upgrades. You can also use mysql or postgres as the Grafana database.
|
||||
The default configuration specifies a sqlite3 database located at
|
||||
`/var/lib/grafana/grafana.db`. Please backup this database before
|
||||
upgrades. You can also use MySQL or Postgres as the Grafana database.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration file is located at `/etc/grafana/grafana.ini`. Go the [Configuration](configuration) page for details
|
||||
on all those options.
|
||||
The configuration file is located at `/etc/grafana/grafana.ini`. Go the
|
||||
[Configuration](/installation/configuration) page for details on all
|
||||
those options.
|
||||
|
||||
### Adding data sources
|
||||
|
||||
|
@ -4,45 +4,70 @@ page_keywords: grafana, support, documentation
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
This page is dedicated to helping you solve any problem you have getting Grafana to work. Please review it before
|
||||
opening a new github issue or asking a question in #grafana on freenode.
|
||||
This page is dedicated to helping you solve any problem you have getting
|
||||
Grafana to work. Please review it before opening a new [GitHub
|
||||
issue](https://github.com/grafana/grafana/issues/new) or asking a
|
||||
question in the `#grafana` IRC channel on freenode.
|
||||
|
||||
## General connection issues
|
||||
When setting up Grafana for the first time you might experiance issues with Grafana being unable to query Graphite, OpenTSDB or InfluxDB.
|
||||
You might not be able to get metric name completion or the graph might show an error like this:
|
||||
|
||||
When setting up Grafana for the first time you might experience issues
|
||||
with Grafana being unable to query Graphite, OpenTSDB or InfluxDB. You
|
||||
might not be able to get metric name completion or the graph might show
|
||||
an error like this:
|
||||
|
||||

|
||||
|
||||
For some type of errors the ``View details`` link will show you error details. But for many types of HTTP connection errors there is
|
||||
very little information. The best way to troubleshoot these issues is use
|
||||
[Chrome developer tools](https://developer.chrome.com/devtools/index). By pressing F12 you can bring up the chrome dev tools.
|
||||
For some type of errors the `View details` link will show you error
|
||||
details. But for many types of HTTP connection errors there is very
|
||||
little information. The best way to troubleshoot these issues is use
|
||||
the [Chrome developer tools](https://developer.chrome.com/devtools/index).
|
||||
By pressing `F12` you can bring up the chrome dev tools.
|
||||
|
||||

|
||||
|
||||
There are two important tabs in the chrome dev tools, ``Network`` and ``Console``. Console will show you javascript errors and HTTP
|
||||
request errors. In the Network tab you will be able to identifiy the request that failed and review request and response parameters.
|
||||
This information will be of great help in finding the cause of the error. If you are unable to solve the issue, even after reading
|
||||
the remainder of this troubleshooting guide, you may open a [github support issue](https://github.com/grafana/grafana/issues).
|
||||
Before you do that please search the existing closed or open issues. Also if you need to create a support issue,
|
||||
screenshots and or text information about the chrome console error, request and response information from the network tab in chrome
|
||||
developer tools are of great help.
|
||||
There are two important tabs in the Chrome developer tools: `Network`
|
||||
and `Console`. The `Console` tab will show you Javascript errors and
|
||||
HTTP request errors. In the Network tab you will be able to identify the
|
||||
request that failed and review request and response parameters. This
|
||||
information will be of great help in finding the cause of the error.
|
||||
|
||||
If you are unable to solve the issue, even after reading the remainder
|
||||
of this troubleshooting guide, you should open a [GitHub support
|
||||
issue](https://github.com/grafana/grafana/issues). Before you do that
|
||||
please search the existing closed or open issues. Also if you need to
|
||||
create a support issue, screen shots and or text information about the
|
||||
chrome console error, request and response information from the
|
||||
`Network` tab in Chrome developer tools are of great help.
|
||||
|
||||
### Inspecting Grafana metric requests
|
||||
|
||||

|
||||
|
||||
After open chrome developer tools for the first time the Network tab is empty you need to refresh the page to get requests to show.
|
||||
For some type of errors (CORS related) there might not be a response at all.
|
||||
After opening the Chrome developer tools for the first time the
|
||||
`Network` tab is empty. You will need to refresh the page to get
|
||||
requests to show. For some type of errors, especially CORS-related,
|
||||
there might not be a response at all.
|
||||
|
||||
## Graphite connection issues
|
||||
If your Graphite web server is on another domain or IP than your Grafana web server you will need to [setup
|
||||
CORS](../install/#graphite-server-config) (Cross Origin Resource Sharing).
|
||||
|
||||
You know if you are having CORS related issues if you get an error like this in chrome developer tools:
|
||||
If your Graphite web server is on another domain or IP address from your
|
||||
Grafana web server you will need to [setup
|
||||
CORS](../install/#graphite-server-config) (Cross Origin Resource
|
||||
Sharing).
|
||||
|
||||
You know if you are having CORS-related issues if you get an error like
|
||||
this in the Chrome developer tools:
|
||||
|
||||

|
||||
|
||||
If the request failed on method ``OPTIONS`` then you need to review your graphite web server configuration.
|
||||
If the request failed on method `OPTIONS` then you need to review your
|
||||
Graphite web server configuration.
|
||||
|
||||
## Only blank white page
|
||||
When you load Grafana and all you get is a blank white page then you probably have a javascript syntax error in ``config.js``.
|
||||
In chrome developer tools console you will quickly identify the line of the syntax error.
|
||||
|
||||
When you load Grafana and all you get is a blank white page then you
|
||||
probably have a Javascript syntax error in `config.js`. In the Chrome
|
||||
developer tools console you will quickly identify the line of the syntax
|
||||
error.
|
||||
|
||||
|
@ -13,21 +13,30 @@ Description | Download
|
||||
Zip package for Windows | [grafana.2.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.0.2.windows-x64.zip)
|
||||
|
||||
## Configure
|
||||
The zip file contains a folder with the current grafana version. Extract this folder to anywhere you want Grafana to run from.
|
||||
Go into the `conf` directory and copy `sample.ini` to `custom.ini`. You should edit `custom.ini`, never `defaults.ini`.
|
||||
|
||||
The default grafana port is `3000`, this port requires extra permissions on windows. Edit `custom.ini` and uncomment the `http_port`
|
||||
config and change it to something like `8080` or similar. That port should not require extra windows privileges.
|
||||
The zip file contains a folder with the current Grafana version. Extract
|
||||
this folder to anywhere you want Grafana to run from. Go into the
|
||||
`conf` directory and copy `sample.ini` to `custom.ini`. You should edit
|
||||
`custom.ini`, never `defaults.ini`.
|
||||
|
||||
Start grafana by executing `grafana-server.exe`, preferbly from the command line. If you want to run Grafana as
|
||||
windows service, download [NSSM](https://nssm.cc/). It is very easy add grafana as windows service using that tool.
|
||||
The default Grafana port is `3000`, this port requires extra permissions
|
||||
on windows. Edit `custom.ini` and uncomment the `http_port`
|
||||
configuration option and change it to something like `8080` or similar.
|
||||
That port should not require extra Windows privileges.
|
||||
|
||||
Read more about the [configuration options](configuration.md).
|
||||
Start Grafana by executing `grafana-server.exe`, preferably from the
|
||||
command line. If you want to run Grafana as windows service, download
|
||||
[NSSM](https://nssm.cc/). It is very easy add Grafana as a Windows
|
||||
service using that tool.
|
||||
|
||||
Read more about the [configuration options](/installation/configuration).
|
||||
|
||||
## Building on Windows
|
||||
|
||||
The Grafana backend includes Sqlite3 which requires GCC to compile. So in order to compile Grafana on windows you need
|
||||
to install GCC. We recommend [TDM-GCC](http://tdm-gcc.tdragon.net/download).
|
||||
The Grafana backend includes Sqlite3 which requires GCC to compile. So
|
||||
in order to compile Grafana on Windows you need to install GCC. We
|
||||
recommend [TDM-GCC](http://tdm-gcc.tdragon.net/download).
|
||||
|
||||
Copy conf/sample.ini to a file named conf/custom.ini and change the web server port to something like 8080. The default
|
||||
Grafana port(3000) requires special privileges on Windows.
|
||||
Copy `conf/sample.ini` to a file named `conf/custom.ini` and change the
|
||||
web server port to something like 8080. The default Grafana port, 3000,
|
||||
requires special privileges on Windows.
|
||||
|
@ -17,6 +17,7 @@ dev environment.
|
||||
## Get Code
|
||||
|
||||
```
|
||||
export GOPATH=`pwd`
|
||||
go get github.com/grafana/grafana
|
||||
```
|
||||
|
||||
@ -24,11 +25,11 @@ go get github.com/grafana/grafana
|
||||
```
|
||||
cd $GOPATH/src/github.com/grafana/grafana
|
||||
go run build.go setup (only needed once to install godep)
|
||||
godep restore (will pull down all golang lib dependecies in your current GOPATH)
|
||||
$GOPATH/bin/godep restore (will pull down all golang lib dependecies in your current GOPATH)
|
||||
go build .
|
||||
```
|
||||
|
||||
# Building on Windows
|
||||
## Building on Windows
|
||||
The Grafana backend includes Sqlite3 which requires GCC to compile. So in order to compile Grafana on windows you need
|
||||
to install GCC. We recommend [TDM-GCC](http://tdm-gcc.tdragon.net/download).
|
||||
|
||||
@ -58,6 +59,8 @@ bra run
|
||||
Open grafana in your browser (default http://localhost:3000) and login with admin user (default user/pass = admin/admin).
|
||||
|
||||
## Creating optimized release packages
|
||||
This step builds linux packages and requires that fpm is installed. Install fpm via `gem install fpm`.
|
||||
|
||||
```
|
||||
go run build.go build package
|
||||
```
|
||||
@ -72,4 +75,4 @@ You only need to add the options you want to override. Config files are applied
|
||||
|
||||
## Create a pull requests
|
||||
|
||||
Before or after your create a pull requests, sign the [contributor license aggrement](/docs/contributing/cla.html).
|
||||
Before or after your create a pull requests, sign the [contributor license agreement](/project/cla.html).
|
||||
|
@ -13,14 +13,14 @@ you can get title, tags, and text information for the event.
|
||||
To add an annotation query click dashboard settings icon in top menu and select `Annotations` from the
|
||||
dropdown. This will open the `Annotations` edit view. Click the `Add` tab to add a new annotation query.
|
||||
|
||||
### Graphite annotations
|
||||
## Graphite annotations
|
||||
|
||||
Graphite supports two ways to query annotations.
|
||||
|
||||
- A regular metric query, use the `Graphite target expression` text input for this
|
||||
- Graphite events query, use the `Graphite event tags` text input, especify an tag or wildcard (leave empty should also work)
|
||||
- Graphite events query, use the `Graphite event tags` text input, specify an tag or wildcard (leave empty should also work)
|
||||
|
||||
## Elasticsearch annoations
|
||||
## Elasticsearch annotations
|
||||

|
||||
|
||||
Grafana can query any Elasticsearch index for annotation events. The index name can be the name of an alias or an index wildcard pattern.
|
||||
@ -36,5 +36,4 @@ as the name for the fields that should be used for the annotation title, tags an
|
||||
|
||||
For InfluxDB you need to enter a query like in the above screenshot. You need to have the ```where $timeFilter``` part.
|
||||
If you only select one column you will not need to enter anything in the column mapping fields.
|
||||
If you have multiple columns you need to specify which column should be treated as title, tags and text column.
|
||||
|
||||
|
@ -6,4 +6,22 @@ page_keywords: grafana, dashlist, panel, documentation
|
||||
|
||||
# Dashlist Panel
|
||||
|
||||
## Overview
|
||||

|
||||
|
||||
The dashboard list panel allows you to show a list of links to other dashboards. The list
|
||||
can be based on a search query or dashboard tag query. You can also configure it to show your starred
|
||||
dashboards.
|
||||
|
||||
## Options
|
||||

|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
Mode | Set search or starred mode
|
||||
Query | If in search mode specify the search query
|
||||
Tags | if in search mode specify dashboard tags to search for
|
||||
Limit number to | Specify the maximum number of dashboards
|
||||
|
||||
|
||||
|
||||
|
@ -62,7 +62,7 @@ The ``Left Y`` and ``Right Y`` can be customized using:
|
||||
|
||||
- ``Unit`` - The display unit for the Y value
|
||||
- ``Grid Max`` - The maximum Y value. (default auto)
|
||||
- ``Grid Min`` - The minium Y value. (default auto)
|
||||
- ``Grid Min`` - The minimum Y value. (default auto)
|
||||
- ``Label`` - The Y axis label (default "")
|
||||
|
||||
Axes can also be hidden by unchecking the appropriate box from `Show Axis`.
|
||||
|
@ -53,7 +53,7 @@ Creates a new dashboard or updates an existing dashboard.
|
||||
"rows": [
|
||||
{
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemaVersion": 6,
|
||||
"version": 0
|
||||
},
|
||||
@ -84,8 +84,8 @@ Status Codes:
|
||||
- **401** – Unauthorized
|
||||
- **412** – Precondition failed
|
||||
|
||||
The **412** status code is used when a newer dashboard already exists (newer, its version is greater than the verison that was sent). The
|
||||
same status code is also used if another dashboar exists with the same title. The response body will look like this:
|
||||
The **412** status code is used when a newer dashboard already exists (newer, its version is greater than the version that was sent). The
|
||||
same status code is also used if another dashboard exists with the same title. The response body will look like this:
|
||||
|
||||
HTTP/1.1 412 Precondition Failed
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
@ -121,7 +121,7 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver
|
||||
"isStarred": false,
|
||||
"slug": "production-overview"
|
||||
},
|
||||
"dashboard": {
|
||||
"model": {
|
||||
"id": null,
|
||||
"title": "Production Overview",
|
||||
"tags": [ "templated" ],
|
||||
@ -141,12 +141,236 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver
|
||||
|
||||
The above will delete the dashboard with the specified slug. The slug is the url friendly (unique) version of the dashboard title.
|
||||
|
||||
### Gets the home dashboard
|
||||
|
||||
`GET /api/dashboards/home`
|
||||
|
||||
### Tags for Dashboard
|
||||
|
||||
`GET /api/dashboards/tags`
|
||||
|
||||
### Dashboard from JSON file
|
||||
|
||||
`GET /file/:file`
|
||||
|
||||
### Search Dashboards
|
||||
|
||||
`GET /api/search/`
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **query** – Search Query
|
||||
- **tags** – Tags to use
|
||||
- **starred** – Flag indicating if only starred Dashboards should be returned
|
||||
- **tagcloud** - Flag indicating if a tagcloud should be returned
|
||||
|
||||
**Example Request**:
|
||||
|
||||
GET /api/search?query=MyDashboard&starred=true&tag=prod HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
## Data sources
|
||||
|
||||
### Get all datasources
|
||||
|
||||
`GET /api/datasources`
|
||||
|
||||
### Get a single data sources by Id
|
||||
|
||||
`GET /api/datasources/:datasourceId`
|
||||
|
||||
### Create data source
|
||||
|
||||
## Organizations
|
||||
`POST /api/datasources`
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Datasource added"}
|
||||
|
||||
### Update an existing data source
|
||||
|
||||
`PUT /api/datasources/:datasourceId`
|
||||
|
||||
### Delete an existing data source
|
||||
|
||||
`DELETE /api/datasources/:datasourceId`
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Data source deleted"}
|
||||
|
||||
### Available data source types
|
||||
|
||||
`GET /api/datasources/plugins`
|
||||
|
||||
## Data source proxy calls
|
||||
|
||||
`GET /api/datasources/proxy/:datasourceId/*`
|
||||
|
||||
Proxies all calls to the actual datasource.
|
||||
|
||||
## Organisation
|
||||
|
||||
### Get current Organisation
|
||||
|
||||
`GET /api/org`
|
||||
|
||||
### Get all users within the actual organisation
|
||||
|
||||
`GET /api/org/users`
|
||||
|
||||
### Add a new user to the actual organisation
|
||||
|
||||
`POST /api/org/users`
|
||||
|
||||
Adds a global user to the actual organisation.
|
||||
|
||||
### Updates the given user
|
||||
|
||||
`PATCH /api/org/users/:userId`
|
||||
|
||||
### Delete user in actual organisation
|
||||
|
||||
`DELETE /api/org/users/:userId`
|
||||
|
||||
### Get all Users
|
||||
|
||||
`GET /api/org/users`
|
||||
|
||||
## Organisations
|
||||
|
||||
### Search all Organisations
|
||||
|
||||
`GET /api/orgs`
|
||||
|
||||
### Update Organisation
|
||||
|
||||
`PUT /api/orgs/:orgId`
|
||||
|
||||
### Get Users in Organisation
|
||||
|
||||
`GET /api/orgs/:orgId/users`
|
||||
|
||||
### Add User in Organisation
|
||||
|
||||
`POST /api/orgs/:orgId/users`
|
||||
|
||||
### Update Users in Organisation
|
||||
|
||||
`PATCH /api/orgs/:orgId/users/:userId`
|
||||
|
||||
### Delete User in Organisation
|
||||
|
||||
`DELETE /api/orgs/:orgId/users/:userId`
|
||||
|
||||
## Users
|
||||
|
||||
### Search Users
|
||||
|
||||
`GET /api/users`
|
||||
|
||||
### Get single user by Id
|
||||
|
||||
`GET /api/users/:id`
|
||||
|
||||
### User Update
|
||||
|
||||
`PUT /api/users/:id`
|
||||
|
||||
### Get Organisations for user
|
||||
|
||||
`GET /api/users/:id/orgs`
|
||||
|
||||
## User
|
||||
|
||||
### Change Password
|
||||
|
||||
`PUT /api/user/password`
|
||||
|
||||
Changes the password for the user
|
||||
|
||||
### Actual User
|
||||
|
||||
`GET /api/user`
|
||||
|
||||
The above will return the current user.
|
||||
|
||||
### Switch user context
|
||||
|
||||
`POST /api/user/using/:organisationId`
|
||||
|
||||
Switch user context to the given organisation.
|
||||
|
||||
### Organisations of the actual User
|
||||
|
||||
`GET /api/user/orgs`
|
||||
|
||||
The above will return a list of all organisations of the current user.
|
||||
|
||||
### Star a dashboard
|
||||
|
||||
`POST /api/user/stars/dashboard/:dashboardId`
|
||||
|
||||
Stars the given Dashboard for the actual user.
|
||||
|
||||
### Unstar a dashboard
|
||||
|
||||
`DELETE /api/user/stars/dashboard/:dashboardId`
|
||||
|
||||
Deletes the staring of the given Dashboard for the actual user.
|
||||
|
||||
## Snapshots
|
||||
|
||||
### Create new snapshot
|
||||
|
||||
`POST /api/snapshots`
|
||||
|
||||
### Get Snapshot by Id
|
||||
|
||||
`GET /api/snapshots/:key`
|
||||
|
||||
### Delete Snapshot by Id
|
||||
|
||||
`DELETE /api/snapshots-delete/:key`
|
||||
|
||||
## Frontend Settings
|
||||
|
||||
### Get Settings
|
||||
|
||||
`GET /api/frontend/settings`
|
||||
|
||||
## Login
|
||||
|
||||
### Renew session based on remember cookie
|
||||
|
||||
`GET /api/login/ping`
|
||||
|
||||
## Admin
|
||||
|
||||
### Settings
|
||||
|
||||
`GET /api/admin/settings`
|
||||
|
||||
### Global Users
|
||||
|
||||
`POST /api/admin/users`
|
||||
|
||||
### Password for User
|
||||
|
||||
`PUT /api/admin/users/:id/password`
|
||||
|
||||
### Permissions
|
||||
|
||||
`PUT /api/admin/users/:id/permissions`
|
||||
|
||||
### Delete global User
|
||||
|
||||
`DELETE /api/admin/users/:id`
|
||||
|
@ -12,7 +12,7 @@ With scripted dashboards you can dynamically create your dashboards using javasc
|
||||
under `public/dashboards/` there is a file named `scripted.js`. This file contains an example of a scripted dashboard. You can access it by using the url:
|
||||
`http://grafana_url/dashboard/script/scripted.js?rows=3&name=myName`
|
||||
|
||||
If you open scripted.js you can see how it reads url paramters from ARGS variable and then adds rows and panels.
|
||||
If you open scripted.js you can see how it reads url parameters from ARGS variable and then adds rows and panels.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -11,5 +11,48 @@ page_keywords: grafana, singlestat, panel, documentation
|
||||
The singlestat panel allows you to show the one main summery stat of a single series (like max, min, avg, sum). It also
|
||||
provides thresholds to color that singlestat metric or the panel background.
|
||||
|
||||
## Options
|
||||
- TODO
|
||||
### Big Value Configuration
|
||||
|
||||
The big value configuration allows you to both customize the look of your singlestat metric, as well as add additional labels to contexualize the metric.
|
||||
|
||||
<img class="no-shadow" src="/img/v1/Singlestat-BaseSettings.png">
|
||||
|
||||
1. `Big Value`: Big Value refers to the collection of values displayed in the singlestat panel.
|
||||
2. `Prefixes`: The Prefix fields let you define a custom label and font-size (as a %) to appear *before* the singlestat metric.
|
||||
3. `Values`: The Value fields let you set the (min, max, average, current, total) and font-size (as a %) of the singlestat metric.
|
||||
4. `Potsfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the singlestat metric.
|
||||
5. `Units`: Units are appended to the the singlestat metric within the panel, and will respect the color and threshold settings for the Value.
|
||||
6. `Decimals`: The Decimal field allows you to override automatic decimal precision, inceasing the digits displayed for your singlestat metric.
|
||||
|
||||
### Coloring
|
||||
|
||||
The coloring options of the singlestat config allow you to dynamically change the colors based on the displayed data.
|
||||
|
||||
<img class="no-shadow" src="/img/v1/Singlestat-Coloring.png">
|
||||
|
||||
1. `Background`: The Background checkbox applies the configured thresholds and colors to the entirity of the singlestat panel background.
|
||||
2. `Value`: The Value checkbox applies the configured thresholds and colors to the value within the singlestat panel.
|
||||
3. `Thresholds`: Thresholds allow you to change the background and value colors dyanmically within the panel. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right.
|
||||
4. `Colors`: The color picker allows you to select a color and opacity
|
||||
5. `Invert order`: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/v1/ryg.png">).
|
||||
|
||||
### Spark Lines
|
||||
|
||||
Spark lines are a great way of seeing the historical data associated with a single stat value, providing valuable context at a glance. Spark lines act differently than traditional graph panels and do not include x or y axis, coordinates, a legend, or ability to interact with the graph.
|
||||
|
||||
<img class="no-shadow" src="/img/v1/Singlestat-Sparklines.png">
|
||||
|
||||
1. `Show`: The show checkbox will toggle whether the spark line is shown in the panel. When unselected, only the value will appear.
|
||||
2. `Background`: Check if you want the sparklines to take up the full panel width or uncheck if they should only be at the bottom.
|
||||
3. `Line Color`: This color selection applies to the color of the sparkline itself.
|
||||
4. `Fill Color`: This color selection applies to the area below the sparkline.
|
||||
|
||||
> ***Pro-tip:*** Reduce the opacity on fill colors for nice looking panels.
|
||||
|
||||
### Value to text mapping
|
||||
|
||||
Value to text mapping allows you to translate values into explcit text. The text will respect all styling, thresholds and customization defined for the value.
|
||||
|
||||
<img class="no-shadow" src="/img/v1/Singlestat-ValueMapping.png">
|
||||
|
||||
|
||||
|
@ -5,14 +5,43 @@ page_keywords: grafana, templating, variables, guide, documentation
|
||||
---
|
||||
|
||||
# Templated Dashboards
|
||||

|
||||
|
||||
Templating feature can be enabled under dashboard settings, in the Features tab. The templating feature allows
|
||||
you to create variables that can be used in your metric queries, series names and panel titles. Use this feature to
|
||||
create generic dashboards that can quickly be changed to show graphs for different servers or metrics.
|
||||
## Overview
|
||||
Templating allows you to create dashboard variables that can be used in your metric queries, series
|
||||
names and panel titles. Use this feature to create generic dashboards that can quickly be
|
||||
changed to show graphs for different servers or metrics.
|
||||
|
||||
You find this feature in the dashboard cog dropdown menu.
|
||||
|
||||
## Variable types
|
||||
There are three different types of template variables. They can all be used in the
|
||||
same way but they differ in how the list variables values is created.
|
||||
|
||||
### Query
|
||||
This is the most common type of variable. It allows you to create a variable
|
||||
with values fetched directly from a data source via a metric exploration query.
|
||||
|
||||
For example a query like `prod.servers.*` will fill the variable with all possible
|
||||
values that exists in the wildcard position (Graphite example).
|
||||
|
||||
You can also create nested variables that use other variables in their definition. For example
|
||||
`apps.$app.servers.*` uses the variable `$app` in its query definition.
|
||||
|
||||
> For examples of template queries appropriate for your data source checkout the documentation
|
||||
> page for your data source.
|
||||
|
||||
### Interval
|
||||
This variable type is useful for time ranges like `1m`,`1h`, `1d`. There is also an auto
|
||||
option that will change depending on the current time range, you can specify how many times
|
||||
the current time range should be divided to calculate the current `auto` range.
|
||||
|
||||

|
||||
|
||||
### Custom
|
||||
This variable type allow you to manually specify all the different values as a comma seperated
|
||||
string.
|
||||
|
||||
## Screencast - Templated Graphite Queries
|
||||
<iframe width="561" height="315" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
<br>
|
||||
## Screencast - Templated InfluxDB Queries
|
||||
Coming soon
|
||||
|
@ -24,7 +24,7 @@ All of this applies to all Panels in the Dashboard (except those with Panel Time
|
||||
|
||||
It's possible to customize the options displayed for relative time and the auto-refresh options.
|
||||
|
||||
From Dashboard setttings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis.
|
||||
From Dashboard settings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis. Entries are comma separated and accept a number followed by one of the following units: s (seconds), m (minutes), h (hours), d (days), w (weeks), M (months), y (years).
|
||||
|
||||

|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
<li><a class='version' href='/v2.1'>Version v2.1</a></li>
|
||||
<li><a class='version' href='/v2.0'>Version v2.0</a></li>
|
||||
<li><a class='version' href='/v1.9'>Version v1.9</a></li>
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2"
|
||||
}
|
||||
|
9
main.go
9
main.go
@ -15,6 +15,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/eventpublisher"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
@ -48,13 +50,18 @@ func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
initRuntime()
|
||||
writePIDFile()
|
||||
initRuntime()
|
||||
|
||||
search.Init()
|
||||
social.NewOAuthService()
|
||||
eventpublisher.Init()
|
||||
plugins.Init()
|
||||
|
||||
if err := notifications.Init(); err != nil {
|
||||
log.Fatal(3, "Notification service failed to initialize", err)
|
||||
}
|
||||
|
||||
if setting.ReportingEnabled {
|
||||
go metrics.StartUsageReportLoop()
|
||||
}
|
||||
|
27
package.json
27
package.json
@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "2.0.2",
|
||||
"version": "2.1.0-pre1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
@ -34,22 +34,19 @@
|
||||
"grunt-string-replace": "~0.2.4",
|
||||
"grunt-usemin": "3.0.0",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"karma": "~0.12.21",
|
||||
"karma": "~0.12.31",
|
||||
"karma-chrome-launcher": "~0.1.4",
|
||||
"karma-coffee-preprocessor": "~0.1.2",
|
||||
"karma-coverage": "^0.2.5",
|
||||
"karma-coveralls": "^0.1.4",
|
||||
"karma-coverage": "0.3.1",
|
||||
"karma-coveralls": "0.1.5",
|
||||
"karma-expect": "~1.1.0",
|
||||
"karma-firefox-launcher": "~0.1.3",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.2.2",
|
||||
"karma-mocha": "~0.1.4",
|
||||
"karma-phantomjs-launcher": "~0.1.4",
|
||||
"karma-requirejs": "~0.2.1",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"load-grunt-tasks": "~0.2.0",
|
||||
"mocha": "~1.16.1",
|
||||
"requirejs": "~2.1.14",
|
||||
"karma-mocha": "~0.1.10",
|
||||
"karma-phantomjs-launcher": "0.1.4",
|
||||
"karma-requirejs": "0.2.2",
|
||||
"karma-script-launcher": "0.1.0",
|
||||
"load-grunt-tasks": "0.2.0",
|
||||
"mocha": "2.2.4",
|
||||
"requirejs": "2.1.17",
|
||||
"rjs-build-analysis": "0.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -65,6 +62,6 @@
|
||||
"grunt-jscs": "~1.5.x",
|
||||
"karma-sinon": "^1.0.3",
|
||||
"lodash": "^2.4.1",
|
||||
"sinon": "^1.10.3"
|
||||
"sinon": "1.10.3"
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,9 @@ case "$1" in
|
||||
chmod 755 /var/log/grafana /var/lib/grafana
|
||||
|
||||
# configuration files should not be modifiable by grafana user, as this can be a security issue
|
||||
chown -Rh root:root /etc/grafana/*
|
||||
chown -Rh root:$GRAFANA_GROUP /etc/grafana/*
|
||||
chmod 755 /etc/grafana
|
||||
find /etc/grafana -type f -exec chmod 644 {} ';'
|
||||
find /etc/grafana -type f -exec chmod 640 {} ';'
|
||||
find /etc/grafana -type d -exec chmod 755 {} ';'
|
||||
|
||||
# if $2 is set, this is an upgrade
|
||||
|
@ -38,7 +38,12 @@ DAEMON=/usr/sbin/$NAME
|
||||
|
||||
if [ `id -u` -ne 0 ]; then
|
||||
echo "You need root privileges to run this script"
|
||||
exit 1
|
||||
exit 4
|
||||
fi
|
||||
|
||||
if [ ! -x $DAEMON ]; then
|
||||
echo "Program not installed or not executable"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
@ -54,9 +59,6 @@ fi
|
||||
|
||||
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
|
||||
|
||||
# Check DAEMON exists
|
||||
test -x $DAEMON || exit 0
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
|
||||
@ -137,8 +139,6 @@ case "$1" in
|
||||
;;
|
||||
*)
|
||||
log_success_msg "Usage: $0 {start|stop|restart|force-reload|status}"
|
||||
exit 1
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
@ -43,9 +43,9 @@ if [ $1 -eq 1 ] ; then
|
||||
chmod 755 /var/log/grafana /var/lib/grafana
|
||||
|
||||
# configuration files should not be modifiable by grafana user, as this can be a security issue
|
||||
chown -Rh root:root /etc/grafana/*
|
||||
chown -Rh root:$GRAFANA_GROUP /etc/grafana/*
|
||||
chmod 755 /etc/grafana
|
||||
find /etc/grafana -type f -exec chmod 644 {} ';'
|
||||
find /etc/grafana -type f -exec chmod 640 {} ';'
|
||||
find /etc/grafana -type d -exec chmod 755 {} ';'
|
||||
|
||||
if [ -x /bin/systemctl ] ; then
|
||||
|
@ -35,6 +35,16 @@ MAX_OPEN_FILES=10000
|
||||
PID_FILE=/var/run/$NAME.pid
|
||||
DAEMON=/usr/sbin/$NAME
|
||||
|
||||
if [ `id -u` -ne 0 ]; then
|
||||
echo "You need root privileges to run this script"
|
||||
exit 4
|
||||
fi
|
||||
|
||||
if [ ! -x $DAEMON ]; then
|
||||
echo "Program not installed or not executable"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
#
|
||||
# init.d / servicectl compatibility (openSUSE)
|
||||
#
|
||||
@ -55,9 +65,6 @@ fi
|
||||
|
||||
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
|
||||
|
||||
# Check DAEMON exists
|
||||
test -x $DAEMON || exit 0
|
||||
|
||||
function isRunning() {
|
||||
status -p $PID_FILE $NAME > /dev/null 2>&1
|
||||
}
|
||||
@ -69,7 +76,7 @@ case "$1" in
|
||||
isRunning
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Already running."
|
||||
exit 2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prepare environment
|
||||
@ -82,7 +89,7 @@ case "$1" in
|
||||
|
||||
# Start Daemon
|
||||
cd $GRAFANA_HOME
|
||||
su -s /bin/sh -c "nohup ${DAEMON} ${DAEMON_OPTS} >> /dev/null 3>&1 &" $GRAFANA_USER
|
||||
su -s /bin/sh -c "nohup ${DAEMON} ${DAEMON_OPTS} >> /dev/null 3>&1 &" $GRAFANA_USER 2> /dev/null
|
||||
return=$?
|
||||
if [ $return -eq 0 ]
|
||||
then
|
||||
@ -90,7 +97,7 @@ case "$1" in
|
||||
# check if pid file has been written two
|
||||
if ! [[ -s $PID_FILE ]]; then
|
||||
echo "FAILED"
|
||||
exit 3
|
||||
exit 1
|
||||
fi
|
||||
i=0
|
||||
timeout=10
|
||||
@ -101,7 +108,7 @@ case "$1" in
|
||||
i=$(($i + 1))
|
||||
if [ $i -gt $timeout ]; then
|
||||
echo "FAILED"
|
||||
exit 4
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@ -131,6 +138,7 @@ case "$1" in
|
||||
;;
|
||||
status)
|
||||
status -p $PID_FILE $NAME
|
||||
exit $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
@ -141,8 +149,6 @@ case "$1" in
|
||||
;;
|
||||
*)
|
||||
echo -n "Usage: $0 {start|stop|restart|force-reload|status}"
|
||||
exit 1
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
@ -17,7 +17,7 @@ func AdminGetSettings(c *middleware.Context) {
|
||||
for _, key := range section.Keys() {
|
||||
keyName := key.Name()
|
||||
value := key.Value()
|
||||
if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") {
|
||||
if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") || (strings.Contains(keyName, "provider_config") && strings.Contains(value, "@")) {
|
||||
value = "************"
|
||||
}
|
||||
|
||||
|
@ -9,36 +9,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func AdminSearchUsers(c *middleware.Context) {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to fetch users", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func AdminGetUser(c *middleware.Context) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
query := m.GetUserByIdQuery{Id: userId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to fetch user", err)
|
||||
return
|
||||
}
|
||||
|
||||
result := dtos.AdminUserListItem{
|
||||
Name: query.Result.Name,
|
||||
Email: query.Result.Email,
|
||||
Login: query.Result.Login,
|
||||
IsGrafanaAdmin: query.Result.IsAdmin,
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
||||
cmd := m.CreateUserCommand{
|
||||
Login: form.Login,
|
||||
@ -70,32 +40,6 @@ func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
||||
c.JsonOK("User created")
|
||||
}
|
||||
|
||||
func AdminUpdateUser(c *middleware.Context, form dtos.AdminUpdateUserForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.UpdateUserCommand{
|
||||
UserId: userId,
|
||||
Login: form.Login,
|
||||
Email: form.Email,
|
||||
Name: form.Name,
|
||||
}
|
||||
|
||||
if len(cmd.Login) == 0 {
|
||||
cmd.Login = cmd.Email
|
||||
if len(cmd.Login) == 0 {
|
||||
c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "failed to update user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User updated")
|
||||
}
|
||||
|
||||
func AdminUpdateUserPassword(c *middleware.Context, form dtos.AdminUpdateUserPasswordForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
|
@ -13,7 +13,7 @@ func Register(r *macaron.Macaron) {
|
||||
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
|
||||
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
reqAccountAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
bind := binding.Bind
|
||||
|
||||
// not logged in views
|
||||
@ -26,6 +26,7 @@ func Register(r *macaron.Macaron) {
|
||||
// authed views
|
||||
r.Get("/profile/", reqSignedIn, Index)
|
||||
r.Get("/org/", reqSignedIn, Index)
|
||||
r.Get("/org/new", reqSignedIn, Index)
|
||||
r.Get("/datasources/", reqSignedIn, Index)
|
||||
r.Get("/datasources/edit/*", reqSignedIn, Index)
|
||||
r.Get("/org/users/", reqSignedIn, Index)
|
||||
@ -39,7 +40,14 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
// sign up
|
||||
r.Get("/signup", Index)
|
||||
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), SignUp)
|
||||
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), wrap(SignUp))
|
||||
|
||||
// reset password
|
||||
r.Get("/user/password/send-reset-email", Index)
|
||||
r.Get("/user/password/reset", Index)
|
||||
|
||||
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
|
||||
|
||||
// dashboard snapshots
|
||||
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
|
||||
@ -53,53 +61,79 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
// authed api
|
||||
r.Group("/api", func() {
|
||||
// user
|
||||
|
||||
// user (signed in)
|
||||
r.Group("/user", func() {
|
||||
r.Get("/", GetUser)
|
||||
r.Put("/", bind(m.UpdateUserCommand{}), UpdateUser)
|
||||
r.Post("/using/:id", UserSetUsingOrg)
|
||||
r.Get("/orgs", GetUserOrgList)
|
||||
r.Post("/stars/dashboard/:id", StarDashboard)
|
||||
r.Delete("/stars/dashboard/:id", UnstarDashboard)
|
||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
|
||||
r.Get("/", wrap(GetSignedInUser))
|
||||
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||
r.Post("/using/:id", wrap(UserSetUsingOrg))
|
||||
r.Get("/orgs", wrap(GetSignedInUserOrgList))
|
||||
r.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
||||
r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
||||
})
|
||||
|
||||
// account
|
||||
// users (admin permission required)
|
||||
r.Group("/users", func() {
|
||||
r.Get("/", wrap(SearchUsers))
|
||||
r.Get("/:id", wrap(GetUserById))
|
||||
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// current org
|
||||
r.Group("/org", func() {
|
||||
r.Get("/", GetOrg)
|
||||
r.Post("/", bind(m.CreateOrgCommand{}), CreateOrg)
|
||||
r.Put("/", bind(m.UpdateOrgCommand{}), UpdateOrg)
|
||||
r.Post("/users", bind(m.AddOrgUserCommand{}), AddOrgUser)
|
||||
r.Get("/users", GetOrgUsers)
|
||||
r.Delete("/users/:id", RemoveOrgUser)
|
||||
}, reqAccountAdmin)
|
||||
r.Get("/", wrap(GetOrgCurrent))
|
||||
r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrgCurrent))
|
||||
r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
||||
r.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||
r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
||||
r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
||||
}, regOrgAdmin)
|
||||
|
||||
// create new org
|
||||
r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||
|
||||
// search all orgs
|
||||
r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))
|
||||
|
||||
// orgs (admin routes)
|
||||
r.Group("/orgs/:orgId", func() {
|
||||
r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrg))
|
||||
r.Get("/users", wrap(GetOrgUsers))
|
||||
r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
|
||||
r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
|
||||
r.Delete("/users/:userId", wrap(RemoveOrgUser))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// auth api keys
|
||||
r.Group("/auth/keys", func() {
|
||||
r.Get("/", GetApiKeys)
|
||||
r.Post("/", bind(m.AddApiKeyCommand{}), AddApiKey)
|
||||
r.Delete("/:id", DeleteApiKey)
|
||||
}, reqAccountAdmin)
|
||||
r.Get("/", wrap(GetApiKeys))
|
||||
r.Post("/", bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
|
||||
r.Delete("/:id", wrap(DeleteApiKey))
|
||||
}, regOrgAdmin)
|
||||
|
||||
// Data sources
|
||||
r.Group("/datasources", func() {
|
||||
r.Combo("/").
|
||||
Get(GetDataSources).
|
||||
Put(bind(m.AddDataSourceCommand{}), AddDataSource).
|
||||
Post(bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
|
||||
r.Get("/", GetDataSources)
|
||||
r.Post("/", bind(m.AddDataSourceCommand{}), AddDataSource)
|
||||
r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
|
||||
r.Delete("/:id", DeleteDataSource)
|
||||
r.Get("/:id", GetDataSourceById)
|
||||
r.Get("/plugins", GetDataSourcePlugins)
|
||||
}, reqAccountAdmin)
|
||||
}, regOrgAdmin)
|
||||
|
||||
r.Get("/frontend/settings/", GetFrontendSettings)
|
||||
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
||||
r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)
|
||||
|
||||
// Dashboard
|
||||
r.Group("/dashboards", func() {
|
||||
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
|
||||
r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
|
||||
r.Get("/file/:file", GetDashboardFromJsonFile)
|
||||
r.Get("/home", GetHomeDashboard)
|
||||
r.Get("/tags", GetDashboardTags)
|
||||
})
|
||||
|
||||
// Search
|
||||
@ -112,10 +146,7 @@ func Register(r *macaron.Macaron) {
|
||||
// admin api
|
||||
r.Group("/api/admin", func() {
|
||||
r.Get("/settings", AdminGetSettings)
|
||||
r.Get("/users", AdminSearchUsers)
|
||||
r.Get("/users/:id", AdminGetUser)
|
||||
r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
||||
r.Put("/users/:id/details", bind(dtos.AdminUpdateUserForm{}), AdminUpdateUser)
|
||||
r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
||||
r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
||||
r.Delete("/users/:id", AdminDeleteUser)
|
||||
@ -124,5 +155,5 @@ func Register(r *macaron.Macaron) {
|
||||
// rendering
|
||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||
|
||||
r.NotFound(NotFound)
|
||||
r.NotFound(NotFoundHandler)
|
||||
}
|
||||
|
35
pkg/api/api_test.go
Normal file
35
pkg/api/api_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpApi(t *testing.T) {
|
||||
|
||||
// Convey("Given the grafana api", t, func() {
|
||||
// ConveyApiScenario("Can sign up", func(c apiTestContext) {
|
||||
// c.PostJson()
|
||||
// So(c.Resp, ShouldEqualJsonApiResponse, "User created and logged in")
|
||||
// })
|
||||
//
|
||||
// m := macaron.New()
|
||||
// m.Use(middleware.GetContextHandler())
|
||||
// m.Use(middleware.Sessioner(&session.Options{}))
|
||||
// Register(m)
|
||||
//
|
||||
// var context *middleware.Context
|
||||
// m.Get("/", func(c *middleware.Context) {
|
||||
// context = c
|
||||
// })
|
||||
//
|
||||
// resp := httptest.NewRecorder()
|
||||
// req, err := http.NewRequest("GET", "/", nil)
|
||||
// So(err, ShouldBeNil)
|
||||
//
|
||||
// m.ServeHTTP(resp, req)
|
||||
//
|
||||
// Convey("should red 200", func() {
|
||||
// So(resp.Code, ShouldEqual, 200)
|
||||
// })
|
||||
// })
|
||||
}
|
@ -8,12 +8,11 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func GetApiKeys(c *middleware.Context) {
|
||||
func GetApiKeys(c *middleware.Context) Response {
|
||||
query := m.GetApiKeysQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to list api keys", err)
|
||||
return
|
||||
return ApiError(500, "Failed to list api keys", err)
|
||||
}
|
||||
|
||||
result := make([]*m.ApiKeyDTO, len(query.Result))
|
||||
@ -24,27 +23,26 @@ func GetApiKeys(c *middleware.Context) {
|
||||
Role: t.Role,
|
||||
}
|
||||
}
|
||||
c.JSON(200, result)
|
||||
|
||||
return Json(200, result)
|
||||
}
|
||||
|
||||
func DeleteApiKey(c *middleware.Context) {
|
||||
func DeleteApiKey(c *middleware.Context) Response {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
|
||||
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to delete API key", err)
|
||||
return
|
||||
return ApiError(500, "Failed to delete API key", err)
|
||||
}
|
||||
|
||||
c.JsonOK("API key deleted")
|
||||
return ApiSuccess("API key deleted")
|
||||
}
|
||||
|
||||
func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
|
||||
func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
c.JsonApiErr(400, "Invalid role specified", nil)
|
||||
return
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
cmd.OrgId = c.OrgId
|
||||
@ -53,14 +51,12 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
|
||||
cmd.Key = newKeyInfo.HashedKey
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to add API key", err)
|
||||
return
|
||||
return ApiError(500, "Failed to add API key", err)
|
||||
}
|
||||
|
||||
result := &dtos.NewApiKeyResult{
|
||||
Name: cmd.Result.Name,
|
||||
Key: newKeyInfo.ClientSecret,
|
||||
}
|
||||
Key: newKeyInfo.ClientSecret}
|
||||
|
||||
c.JSON(200, result)
|
||||
return Json(200, result)
|
||||
}
|
||||
|
122
pkg/api/common.go
Normal file
122
pkg/api/common.go
Normal file
@ -0,0 +1,122 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
NotFound = ApiError(404, "Not found", nil)
|
||||
ServerError = ApiError(500, "Server error", nil)
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
WriteTo(out http.ResponseWriter)
|
||||
}
|
||||
|
||||
type NormalResponse struct {
|
||||
status int
|
||||
body []byte
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func wrap(action interface{}) macaron.Handler {
|
||||
|
||||
return func(c *middleware.Context) {
|
||||
var res Response
|
||||
val, err := c.Invoke(action)
|
||||
if err == nil && val != nil && len(val) > 0 {
|
||||
res = val[0].Interface().(Response)
|
||||
} else {
|
||||
res = ServerError
|
||||
}
|
||||
|
||||
res.WriteTo(c.Resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NormalResponse) WriteTo(out http.ResponseWriter) {
|
||||
header := out.Header()
|
||||
for k, v := range r.header {
|
||||
header[k] = v
|
||||
}
|
||||
out.WriteHeader(r.status)
|
||||
out.Write(r.body)
|
||||
}
|
||||
|
||||
func (r *NormalResponse) Cache(ttl string) *NormalResponse {
|
||||
return r.Header("Cache-Control", "public,max-age="+ttl)
|
||||
}
|
||||
|
||||
func (r *NormalResponse) Header(key, value string) *NormalResponse {
|
||||
r.header.Set(key, value)
|
||||
return r
|
||||
}
|
||||
|
||||
// functions to create responses
|
||||
|
||||
func Empty(status int) *NormalResponse {
|
||||
return Respond(status, nil)
|
||||
}
|
||||
|
||||
func Json(status int, body interface{}) *NormalResponse {
|
||||
return Respond(status, body).Header("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
func ApiSuccess(message string) *NormalResponse {
|
||||
resp := make(map[string]interface{})
|
||||
resp["message"] = message
|
||||
return Respond(200, resp)
|
||||
}
|
||||
|
||||
func ApiError(status int, message string, err error) *NormalResponse {
|
||||
resp := make(map[string]interface{})
|
||||
|
||||
if err != nil {
|
||||
log.Error(4, "%s: %v", message, err)
|
||||
if setting.Env != setting.PROD {
|
||||
resp["error"] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
switch status {
|
||||
case 404:
|
||||
metrics.M_Api_Status_404.Inc(1)
|
||||
resp["message"] = "Not Found"
|
||||
case 500:
|
||||
metrics.M_Api_Status_500.Inc(1)
|
||||
resp["message"] = "Internal Server Error"
|
||||
}
|
||||
|
||||
if message != "" {
|
||||
resp["message"] = message
|
||||
}
|
||||
|
||||
return Json(status, resp)
|
||||
}
|
||||
|
||||
func Respond(status int, body interface{}) *NormalResponse {
|
||||
var b []byte
|
||||
var err error
|
||||
switch t := body.(type) {
|
||||
case []byte:
|
||||
b = t
|
||||
case string:
|
||||
b = []byte(t)
|
||||
default:
|
||||
if b, err = json.Marshal(body); err != nil {
|
||||
return ApiError(500, "body json marshal", err)
|
||||
}
|
||||
}
|
||||
return &NormalResponse{
|
||||
body: b,
|
||||
status: status,
|
||||
header: make(http.Header),
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -30,7 +32,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
|
||||
func GetDashboard(c *middleware.Context) {
|
||||
metrics.M_Api_Dashboard_Get.Inc(1)
|
||||
|
||||
slug := c.Params(":slug")
|
||||
slug := strings.ToLower(c.Params(":slug"))
|
||||
|
||||
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
@ -46,9 +48,16 @@ func GetDashboard(c *middleware.Context) {
|
||||
}
|
||||
|
||||
dash := query.Result
|
||||
dto := dtos.Dashboard{
|
||||
Model: dash.Data,
|
||||
Meta: dtos.DashboardMeta{IsStarred: isStarred, Slug: slug},
|
||||
dto := dtos.DashboardFullWithMeta{
|
||||
Dashboard: dash.Data,
|
||||
Meta: dtos.DashboardMeta{
|
||||
IsStarred: isStarred,
|
||||
Slug: slug,
|
||||
Type: m.DashTypeDB,
|
||||
CanStar: c.IsSignedIn,
|
||||
CanSave: c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR,
|
||||
CanEdit: c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR || c.OrgRole == m.ROLE_READ_ONLY_EDITOR,
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(200, dto)
|
||||
@ -87,6 +96,10 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
|
||||
c.JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
if err == m.ErrDashboardNotFound {
|
||||
c.JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JsonApiErr(500, "Failed to save dashboard", err)
|
||||
return
|
||||
}
|
||||
@ -104,13 +117,39 @@ func GetHomeDashboard(c *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dash := dtos.Dashboard{}
|
||||
dash := dtos.DashboardFullWithMeta{}
|
||||
dash.Meta.IsHome = true
|
||||
jsonParser := json.NewDecoder(file)
|
||||
if err := jsonParser.Decode(&dash.Model); err != nil {
|
||||
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
|
||||
c.JsonApiErr(500, "Failed to load home dashboard", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, &dash)
|
||||
}
|
||||
|
||||
func GetDashboardFromJsonFile(c *middleware.Context) {
|
||||
file := c.Params(":file")
|
||||
|
||||
dashboard := search.GetDashboardFromJsonIndex(file)
|
||||
if dashboard == nil {
|
||||
c.JsonApiErr(404, "Dashboard not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
dash := dtos.DashboardFullWithMeta{Dashboard: dashboard.Data}
|
||||
dash.Meta.Type = m.DashTypeJson
|
||||
|
||||
c.JSON(200, &dash)
|
||||
}
|
||||
|
||||
func GetDashboardTags(c *middleware.Context) {
|
||||
query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get tags from database", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapsho
|
||||
}
|
||||
|
||||
func GetDashboardSnapshot(c *middleware.Context) {
|
||||
key := c.Params(":key")
|
||||
|
||||
key := c.Params(":key")
|
||||
query := &m.GetDashboardSnapshotQuery{Key: key}
|
||||
|
||||
err := bus.Dispatch(query)
|
||||
@ -59,13 +59,14 @@ func GetDashboardSnapshot(c *middleware.Context) {
|
||||
|
||||
// expired snapshots should also be removed from db
|
||||
if snapshot.Expires.Before(time.Now()) {
|
||||
c.JsonApiErr(404, "Snapshot not found", err)
|
||||
c.JsonApiErr(404, "Dashboard snapshot not found", err)
|
||||
return
|
||||
}
|
||||
|
||||
dto := dtos.Dashboard{
|
||||
Model: snapshot.Dashboard,
|
||||
dto := dtos.DashboardFullWithMeta{
|
||||
Dashboard: snapshot.Dashboard,
|
||||
Meta: dtos.DashboardMeta{
|
||||
Type: m.DashTypeSnapshot,
|
||||
IsSnapshot: true,
|
||||
Created: snapshot.Created,
|
||||
Expires: snapshot.Expires,
|
||||
|
@ -1,9 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
@ -11,6 +14,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var dataProxyTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy {
|
||||
target, _ := url.Parse(ds.Url)
|
||||
|
||||
@ -56,5 +69,6 @@ func ProxyDataSourceRequest(c *middleware.Context) {
|
||||
|
||||
proxyPath := c.Params("*")
|
||||
proxy := NewReverseProxy(&query.Result, proxyPath)
|
||||
proxy.Transport = dataProxyTransport
|
||||
proxy.ServeHTTP(c.RW(), c.Req.Request)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetDataSources(c *middleware.Context) {
|
||||
@ -94,11 +95,12 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("Datasource added")
|
||||
c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id})
|
||||
}
|
||||
|
||||
func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.Id = c.ParamsInt64(":id")
|
||||
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
|
@ -21,24 +21,29 @@ type CurrentUser struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
}
|
||||
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool `json:"isStarred"`
|
||||
IsHome bool `json:"isHome"`
|
||||
IsSnapshot bool `json:"isSnapshot"`
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsHome bool `json:"isHome,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanStar bool `json:"canStar"`
|
||||
Slug string `json:"slug"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
type Dashboard struct {
|
||||
Meta DashboardMeta `json:"meta"`
|
||||
Model map[string]interface{} `json:"model"`
|
||||
type DashboardFullWithMeta struct {
|
||||
Meta DashboardMeta `json:"meta"`
|
||||
Dashboard map[string]interface{} `json:"dashboard"`
|
||||
}
|
||||
|
||||
type DataSource struct {
|
||||
|
@ -18,7 +18,7 @@ type AdminUpdateUserPasswordForm struct {
|
||||
}
|
||||
|
||||
type AdminUpdateUserPermissionsForm struct {
|
||||
IsGrafanaAdmin bool `json:"IsGrafanaAdmin" binding:"Required"`
|
||||
IsGrafanaAdmin bool `json:"IsGrafanaAdmin"`
|
||||
}
|
||||
|
||||
type AdminUserListItem struct {
|
||||
@ -27,3 +27,13 @@ type AdminUserListItem struct {
|
||||
Login string `json:"login"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
}
|
||||
|
||||
type SendResetPasswordEmailForm struct {
|
||||
UserOrEmail string `json:"userOrEmail" binding:"Required"`
|
||||
}
|
||||
|
||||
type ResetUserPasswordForm struct {
|
||||
Code string `json:"code"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
ConfirmPassword string `json:"confirmPassword"`
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
defaultDatasource = ds.Name
|
||||
}
|
||||
|
||||
if len(ds.JsonData) > 0 {
|
||||
dsMap["jsonData"] = ds.JsonData
|
||||
}
|
||||
|
||||
if ds.Access == m.DS_ACCESS_DIRECT {
|
||||
if ds.BasicAuth {
|
||||
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword)
|
||||
@ -95,6 +99,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
"defaultDatasource": defaultDatasource,
|
||||
"datasources": datasources,
|
||||
"appSubUrl": setting.AppSubUrl,
|
||||
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
"buildInfo": map[string]interface{}{
|
||||
"version": setting.BuildVersion,
|
||||
"commit": setting.BuildCommit,
|
||||
|
@ -18,12 +18,17 @@ func setIndexViewData(c *middleware.Context) error {
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
LightTheme: c.Theme == "light",
|
||||
OrgId: c.OrgId,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
}
|
||||
|
||||
if setting.DisableGravatar {
|
||||
currentUser.GravatarUrl = setting.AppSubUrl + "/img/user_profile.png"
|
||||
}
|
||||
|
||||
if len(currentUser.Name) == 0 {
|
||||
currentUser.Name = currentUser.Login
|
||||
}
|
||||
@ -54,7 +59,7 @@ func Index(c *middleware.Context) {
|
||||
c.HTML(200, "index")
|
||||
}
|
||||
|
||||
func NotFound(c *middleware.Context) {
|
||||
func NotFoundHandler(c *middleware.Context) {
|
||||
if c.IsApiRequest() {
|
||||
c.JsonApiErr(404, "Not found", nil)
|
||||
return
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@ -45,7 +46,13 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
|
||||
userInfo, err := connect.UserInfo(token)
|
||||
if err != nil {
|
||||
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
|
||||
if err == social.ErrMissingTeamMembership {
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github team membership not fulfilled"))
|
||||
} else if err == social.ErrMissingOrganizationMembership {
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github organization membership not fulfilled"))
|
||||
} else {
|
||||
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -54,7 +61,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
// validate that the email is allowed to login to grafana
|
||||
if !connect.IsEmailAllowed(userInfo.Email) {
|
||||
log.Info("OAuth login attempt with unallowed email, %s", userInfo.Email)
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?email_not_allowed=1")
|
||||
ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required email domain not fulfilled"))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -6,19 +6,28 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func GetOrg(c *middleware.Context) {
|
||||
query := m.GetOrgByIdQuery{Id: c.OrgId}
|
||||
// GET /api/org
|
||||
func GetOrgCurrent(c *middleware.Context) Response {
|
||||
return getOrgHelper(c.OrgId)
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId
|
||||
func GetOrgById(c *middleware.Context) Response {
|
||||
return getOrgHelper(c.ParamsInt64(":orgId"))
|
||||
}
|
||||
|
||||
func getOrgHelper(orgId int64) Response {
|
||||
query := m.GetOrgByIdQuery{Id: orgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrOrgNotFound {
|
||||
c.JsonApiErr(404, "Organization not found", err)
|
||||
return
|
||||
return ApiError(404, "Organization not found", err)
|
||||
}
|
||||
|
||||
c.JsonApiErr(500, "Failed to get organization", err)
|
||||
return
|
||||
return ApiError(500, "Failed to get organization", err)
|
||||
}
|
||||
|
||||
org := m.OrgDTO{
|
||||
@ -26,33 +35,59 @@ func GetOrg(c *middleware.Context) {
|
||||
Name: query.Result.Name,
|
||||
}
|
||||
|
||||
c.JSON(200, &org)
|
||||
return Json(200, &org)
|
||||
}
|
||||
|
||||
func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) {
|
||||
if !setting.AllowUserOrgCreate && !c.IsGrafanaAdmin {
|
||||
c.JsonApiErr(401, "Access denied", nil)
|
||||
return
|
||||
// POST /api/orgs
|
||||
func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response {
|
||||
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
|
||||
return ApiError(401, "Access denied", nil)
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to create organization", err)
|
||||
return
|
||||
return ApiError(500, "Failed to create organization", err)
|
||||
}
|
||||
|
||||
metrics.M_Api_Org_Create.Inc(1)
|
||||
|
||||
c.JsonOK("Organization created")
|
||||
return Json(200, &util.DynMap{
|
||||
"orgId": cmd.Result.Id,
|
||||
"message": "Organization created",
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateOrg(c *middleware.Context, cmd m.UpdateOrgCommand) {
|
||||
// PUT /api/org
|
||||
func UpdateOrgCurrent(c *middleware.Context, cmd m.UpdateOrgCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
return updateOrgHelper(cmd)
|
||||
}
|
||||
|
||||
// PUT /api/orgs/:orgId
|
||||
func UpdateOrg(c *middleware.Context, cmd m.UpdateOrgCommand) Response {
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
return updateOrgHelper(cmd)
|
||||
}
|
||||
|
||||
func updateOrgHelper(cmd m.UpdateOrgCommand) Response {
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to update organization", err)
|
||||
return
|
||||
return ApiError(500, "Failed to update organization", err)
|
||||
}
|
||||
|
||||
c.JsonOK("Organization updated")
|
||||
return ApiSuccess("Organization updated")
|
||||
}
|
||||
|
||||
func SearchOrgs(c *middleware.Context) Response {
|
||||
query := m.SearchOrgsQuery{
|
||||
Query: c.Query("query"),
|
||||
Name: c.Query("name"),
|
||||
Page: 0,
|
||||
Limit: 1000,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to search orgs", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
@ -6,60 +6,115 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) {
|
||||
// POST /api/org/users
|
||||
func AddOrgUserToCurrentOrg(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
return addOrgUserHelper(cmd)
|
||||
}
|
||||
|
||||
// POST /api/orgs/:orgId/users
|
||||
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
return addOrgUserHelper(cmd)
|
||||
}
|
||||
|
||||
func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
c.JsonApiErr(400, "Invalid role specified", nil)
|
||||
return
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
|
||||
err := bus.Dispatch(&userQuery)
|
||||
if err != nil {
|
||||
c.JsonApiErr(404, "User not found", nil)
|
||||
return
|
||||
return ApiError(404, "User not found", nil)
|
||||
}
|
||||
|
||||
userToAdd := userQuery.Result
|
||||
|
||||
if userToAdd.Id == c.UserId {
|
||||
c.JsonApiErr(400, "Cannot add yourself as user", nil)
|
||||
return
|
||||
}
|
||||
// if userToAdd.Id == c.UserId {
|
||||
// return ApiError(400, "Cannot add yourself as user", nil)
|
||||
// }
|
||||
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.UserId = userToAdd.Id
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Could not add user to organization", err)
|
||||
return
|
||||
return ApiError(500, "Could not add user to organization", err)
|
||||
}
|
||||
|
||||
c.JsonOK("User added to organization")
|
||||
return ApiSuccess("User added to organization")
|
||||
}
|
||||
|
||||
func GetOrgUsers(c *middleware.Context) {
|
||||
query := m.GetOrgUsersQuery{OrgId: c.OrgId}
|
||||
// GET /api/org/users
|
||||
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
|
||||
return getOrgUsersHelper(c.OrgId)
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId/users
|
||||
func GetOrgUsers(c *middleware.Context) Response {
|
||||
return getOrgUsersHelper(c.ParamsInt64(":orgId"))
|
||||
}
|
||||
|
||||
func getOrgUsersHelper(orgId int64) Response {
|
||||
query := m.GetOrgUsersQuery{OrgId: orgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get account user", err)
|
||||
return
|
||||
return ApiError(500, "Failed to get account user", err)
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
func RemoveOrgUser(c *middleware.Context) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
// PATCH /api/org/users/:userId
|
||||
func UpdateOrgUserForCurrentOrg(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.UserId = c.ParamsInt64(":userId")
|
||||
return updateOrgUserHelper(cmd)
|
||||
}
|
||||
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: c.OrgId, UserId: userId}
|
||||
// PATCH /api/orgs/:orgId/users/:userId
|
||||
func UpdateOrgUser(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
cmd.UserId = c.ParamsInt64(":userId")
|
||||
return updateOrgUserHelper(cmd)
|
||||
}
|
||||
|
||||
func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
c.JsonApiErr(400, "Cannot remove last organization admin", nil)
|
||||
return
|
||||
return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
|
||||
}
|
||||
c.JsonApiErr(500, "Failed to remove user from organization", err)
|
||||
return ApiError(500, "Failed update org user", err)
|
||||
}
|
||||
|
||||
c.JsonOK("User removed from organization")
|
||||
return ApiSuccess("Organization user updated")
|
||||
}
|
||||
|
||||
// DELETE /api/org/users/:userId
|
||||
func RemoveOrgUserForCurrentOrg(c *middleware.Context) Response {
|
||||
userId := c.ParamsInt64(":userId")
|
||||
return removeOrgUserHelper(c.OrgId, userId)
|
||||
}
|
||||
|
||||
// DELETE /api/orgs/:orgId/users/:userId
|
||||
func RemoveOrgUser(c *middleware.Context) Response {
|
||||
userId := c.ParamsInt64(":userId")
|
||||
orgId := c.ParamsInt64(":orgId")
|
||||
return removeOrgUserHelper(orgId, userId)
|
||||
}
|
||||
|
||||
func removeOrgUserHelper(orgId int64, userId int64) Response {
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
return ApiError(400, "Cannot remove last organization admin", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to remove user from organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User removed from organization")
|
||||
}
|
||||
|
49
pkg/api/password.go
Normal file
49
pkg/api/password.go
Normal file
@ -0,0 +1,49 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEmailForm) Response {
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
return ApiError(404, "User does not exist", err)
|
||||
}
|
||||
|
||||
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
|
||||
if err := bus.Dispatch(&emailCmd); err != nil {
|
||||
return ApiError(500, "Failed to send email", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Email sent")
|
||||
}
|
||||
|
||||
func ResetPassword(c *middleware.Context, form dtos.ResetUserPasswordForm) Response {
|
||||
query := m.ValidateResetPasswordCodeQuery{Code: form.Code}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrInvalidEmailCode {
|
||||
return ApiError(400, "Invalid or expired reset password code", nil)
|
||||
}
|
||||
return ApiError(500, "Unknown error validating email code", err)
|
||||
}
|
||||
|
||||
if form.NewPassword != form.ConfirmPassword {
|
||||
return ApiError(400, "Passwords do not match", nil)
|
||||
}
|
||||
|
||||
cmd := m.ChangeUserPasswordCommand{}
|
||||
cmd.UserId = query.Result.Id
|
||||
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to change user password", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User password changed")
|
||||
}
|
@ -3,79 +3,33 @@ package api
|
||||
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/services/search"
|
||||
)
|
||||
|
||||
// TODO: this needs to be cached or improved somehow
|
||||
func setIsStarredFlagOnSearchResults(c *middleware.Context, hits []*m.DashboardSearchHit) error {
|
||||
if !c.IsSignedIn {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := m.GetUserStarsQuery{UserId: c.UserId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dash := range hits {
|
||||
if _, exists := query.Result[dash.Id]; exists {
|
||||
dash.IsStarred = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Search(c *middleware.Context) {
|
||||
query := c.Query("query")
|
||||
tag := c.Query("tag")
|
||||
tagcloud := c.Query("tagcloud")
|
||||
tags := c.QueryStrings("tag")
|
||||
starred := c.Query("starred")
|
||||
limit := c.QueryInt("limit")
|
||||
|
||||
if limit == 0 {
|
||||
limit = 200
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
result := m.SearchResult{
|
||||
Dashboards: []*m.DashboardSearchHit{},
|
||||
Tags: []*m.DashboardTagCloudItem{},
|
||||
searchQuery := search.Query{
|
||||
Title: query,
|
||||
Tags: tags,
|
||||
UserId: c.UserId,
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
if tagcloud == "true" {
|
||||
|
||||
query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get tags from database", err)
|
||||
return
|
||||
}
|
||||
result.Tags = query.Result
|
||||
result.TagsOnly = true
|
||||
|
||||
} else {
|
||||
query := m.SearchDashboardsQuery{
|
||||
Title: query,
|
||||
Tag: tag,
|
||||
UserId: c.UserId,
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Search failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := setIsStarredFlagOnSearchResults(c, query.Result); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get user stars", err)
|
||||
return
|
||||
}
|
||||
|
||||
result.Dashboards = query.Result
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Search failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
c.JSON(200, searchQuery.Result)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/events"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
@ -9,24 +10,29 @@ import (
|
||||
)
|
||||
|
||||
// POST /api/user/signup
|
||||
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) {
|
||||
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) Response {
|
||||
if !setting.AllowUserSignUp {
|
||||
c.JsonApiErr(401, "User signup is disabled", nil)
|
||||
return
|
||||
return ApiError(401, "User signup is disabled", nil)
|
||||
}
|
||||
|
||||
cmd.Login = cmd.Email
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "failed to create user", err)
|
||||
return
|
||||
return ApiError(500, "failed to create user", err)
|
||||
}
|
||||
|
||||
user := cmd.Result
|
||||
|
||||
bus.Publish(&events.UserSignedUp{
|
||||
Id: user.Id,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Login: user.Login,
|
||||
})
|
||||
|
||||
loginUserWithUser(&user, c)
|
||||
|
||||
c.JsonOK("User created and logged in")
|
||||
|
||||
metrics.M_Api_User_SignUp.Inc(1)
|
||||
|
||||
return ApiSuccess("User created and logged in")
|
||||
}
|
||||
|
@ -6,40 +6,35 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func StarDashboard(c *middleware.Context) {
|
||||
var cmd = m.StarDashboardCommand{
|
||||
UserId: c.UserId,
|
||||
DashboardId: c.ParamsInt64(":id"),
|
||||
func StarDashboard(c *middleware.Context) Response {
|
||||
if !c.IsSignedIn {
|
||||
return ApiError(412, "You need to sign in to star dashboards", nil)
|
||||
}
|
||||
|
||||
cmd := m.StarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
|
||||
|
||||
if cmd.DashboardId <= 0 {
|
||||
c.JsonApiErr(400, "Missing dashboard id", nil)
|
||||
return
|
||||
return ApiError(400, "Missing dashboard id", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to star dashboard", err)
|
||||
return
|
||||
return ApiError(500, "Failed to star dashboard", err)
|
||||
}
|
||||
|
||||
c.JsonOK("Dashboard starred!")
|
||||
return ApiSuccess("Dashboard starred!")
|
||||
}
|
||||
|
||||
func UnstarDashboard(c *middleware.Context) {
|
||||
var cmd = m.UnstarDashboardCommand{
|
||||
UserId: c.UserId,
|
||||
DashboardId: c.ParamsInt64(":id"),
|
||||
}
|
||||
func UnstarDashboard(c *middleware.Context) Response {
|
||||
|
||||
cmd := m.UnstarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
|
||||
|
||||
if cmd.DashboardId <= 0 {
|
||||
c.JsonApiErr(400, "Missing dashboard id", nil)
|
||||
return
|
||||
return ApiError(400, "Missing dashboard id", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to unstar dashboard", err)
|
||||
return
|
||||
return ApiError(500, "Failed to unstar dashboard", err)
|
||||
}
|
||||
|
||||
c.JsonOK("Dashboard unstarred")
|
||||
return ApiSuccess("Dashboard unstarred")
|
||||
}
|
||||
|
127
pkg/api/user.go
127
pkg/api/user.go
@ -7,44 +7,71 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetUser(c *middleware.Context) {
|
||||
query := m.GetUserProfileQuery{UserId: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
// GET /api/user (current authenticated user)
|
||||
func GetSignedInUser(c *middleware.Context) Response {
|
||||
return getUserUserProfile(c.UserId)
|
||||
}
|
||||
|
||||
func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) {
|
||||
// GET /api/user/:id
|
||||
func GetUserById(c *middleware.Context) Response {
|
||||
return getUserUserProfile(c.ParamsInt64(":id"))
|
||||
}
|
||||
|
||||
func getUserUserProfile(userId int64) Response {
|
||||
query := m.GetUserProfileQuery{UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get user", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
// POST /api/user
|
||||
func UpdateSignedInUser(c *middleware.Context, cmd m.UpdateUserCommand) Response {
|
||||
cmd.UserId = c.UserId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(400, "Failed to update user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User updated")
|
||||
return handleUpdateUser(cmd)
|
||||
}
|
||||
|
||||
func GetUserOrgList(c *middleware.Context) {
|
||||
query := m.GetUserOrgListQuery{UserId: c.UserId}
|
||||
// POST /api/users/:id
|
||||
func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) Response {
|
||||
cmd.UserId = c.ParamsInt64(":id")
|
||||
return handleUpdateUser(cmd)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get user organizations", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ac := range query.Result {
|
||||
if ac.OrgId == c.OrgId {
|
||||
ac.IsUsing = true
|
||||
break
|
||||
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
|
||||
if len(cmd.Login) == 0 {
|
||||
cmd.Login = cmd.Email
|
||||
if len(cmd.Login) == 0 {
|
||||
return ApiError(400, "Validation error, need specify either username or email", nil)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "failed to update user", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User updated")
|
||||
}
|
||||
|
||||
// GET /api/user/orgs
|
||||
func GetSignedInUserOrgList(c *middleware.Context) Response {
|
||||
return getUserOrgList(c.UserId)
|
||||
}
|
||||
|
||||
// GET /api/user/:id/orgs
|
||||
func GetUserOrgList(c *middleware.Context) Response {
|
||||
return getUserOrgList(c.ParamsInt64(":id"))
|
||||
}
|
||||
|
||||
func getUserOrgList(userId int64) Response {
|
||||
query := m.GetUserOrgListQuery{UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Faile to get user organziations", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
@ -65,53 +92,55 @@ func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
return valid
|
||||
}
|
||||
|
||||
func UserSetUsingOrg(c *middleware.Context) {
|
||||
// POST /api/user/using/:id
|
||||
func UserSetUsingOrg(c *middleware.Context) Response {
|
||||
orgId := c.ParamsInt64(":id")
|
||||
|
||||
if !validateUsingOrg(c.UserId, orgId) {
|
||||
c.JsonApiErr(401, "Not a valid organization", nil)
|
||||
return
|
||||
return ApiError(401, "Not a valid organization", nil)
|
||||
}
|
||||
|
||||
cmd := m.SetUsingOrgCommand{
|
||||
UserId: c.UserId,
|
||||
OrgId: orgId,
|
||||
}
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed change active organization", err)
|
||||
return
|
||||
return ApiError(500, "Failed change active organization", err)
|
||||
}
|
||||
|
||||
c.JsonOK("Active organization changed")
|
||||
return ApiSuccess("Active organization changed")
|
||||
}
|
||||
|
||||
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) {
|
||||
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) Response {
|
||||
userQuery := m.GetUserByIdQuery{Id: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
c.JsonApiErr(500, "Could not read user from database", err)
|
||||
return
|
||||
return ApiError(500, "Could not read user from database", err)
|
||||
}
|
||||
|
||||
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
|
||||
if passwordHashed != userQuery.Result.Password {
|
||||
c.JsonApiErr(401, "Invalid old password", nil)
|
||||
return
|
||||
return ApiError(401, "Invalid old password", nil)
|
||||
}
|
||||
|
||||
if len(cmd.NewPassword) < 4 {
|
||||
c.JsonApiErr(400, "New password too short", nil)
|
||||
return
|
||||
return ApiError(400, "New password too short", nil)
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to change user password", err)
|
||||
return
|
||||
return ApiError(500, "Failed to change user password", err)
|
||||
}
|
||||
|
||||
c.JsonOK("User password changed")
|
||||
return ApiSuccess("User password changed")
|
||||
}
|
||||
|
||||
// GET /api/users
|
||||
func SearchUsers(c *middleware.Context) Response {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to fetch users", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -39,7 +39,7 @@ func (b *InProcBus) Dispatch(msg Msg) error {
|
||||
|
||||
var handler = b.handlers[msgName]
|
||||
if handler == nil {
|
||||
return errors.New("handler not found")
|
||||
return fmt.Errorf("handler not found for %s", msgName)
|
||||
}
|
||||
|
||||
var params = make([]reflect.Value, 1)
|
||||
@ -121,3 +121,7 @@ func Dispatch(msg Msg) error {
|
||||
func Publish(msg Msg) error {
|
||||
return globalBus.Publish(msg)
|
||||
}
|
||||
|
||||
func ClearBusHandlers() {
|
||||
globalBus = New()
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ func newMacaron() *macaron.Macaron {
|
||||
mapStatic(m, "css", "css")
|
||||
mapStatic(m, "img", "img")
|
||||
mapStatic(m, "fonts", "fonts")
|
||||
mapStatic(m, "robots.txt", "robots.txxt")
|
||||
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "views"),
|
||||
@ -40,8 +41,12 @@ func newMacaron() *macaron.Macaron {
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
|
||||
if setting.EnforceDomain {
|
||||
m.Use(middleware.ValidateHostHeader(setting.Domain))
|
||||
}
|
||||
|
||||
m.Use(middleware.GetContextHandler())
|
||||
m.Use(middleware.Sessioner(setting.SessionOptions))
|
||||
m.Use(middleware.Sessioner(&setting.SessionOptions))
|
||||
|
||||
return m
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
||||
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
|
||||
pngPath = pngPath + ".png"
|
||||
|
||||
cmd := exec.Command(binPath, "--ignore-ssl-errors=true", scriptPath, "url="+params.Url, "width="+params.Width,
|
||||
cmd := exec.Command(binPath, "--ignore-ssl-errors=true", "--ssl-protocol=any", scriptPath, "url="+params.Url, "width="+params.Width,
|
||||
"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
|
||||
"domain="+setting.Domain, "sessionid="+params.SessionId)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
@ -54,7 +54,7 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
case <-time.After(15 * time.Second):
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Error(4, "failed to kill: %v", err)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Events can be passed to external systems via for example AMPQ
|
||||
// Events can be passed to external systems via for example AMQP
|
||||
// Treat these events as basically DTOs so changes has to be backward compatible
|
||||
|
||||
type Priority string
|
||||
@ -70,6 +70,14 @@ type UserCreated struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type UserSignedUp struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type UserUpdated struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Id int64 `json:"id"`
|
||||
|
71
pkg/middleware/auth_proxy.go
Normal file
71
pkg/middleware/auth_proxy.go
Normal file
@ -0,0 +1,71 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func initContextWithAuthProxy(ctx *Context) bool {
|
||||
if !setting.AuthProxyEnabled {
|
||||
return false
|
||||
}
|
||||
|
||||
proxyHeaderValue := ctx.Req.Header.Get(setting.AuthProxyHeaderName)
|
||||
if len(proxyHeaderValue) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
if err != m.ErrUserNotFound {
|
||||
ctx.Handle(500, "Failed find user specifed in auth proxy header", err)
|
||||
return true
|
||||
}
|
||||
|
||||
if setting.AuthProxyAutoSignUp {
|
||||
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
|
||||
return true
|
||||
}
|
||||
query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id}
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
ctx.Handle(500, "Failed find user after creation", err)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SignedInUser = query.Result
|
||||
ctx.IsSignedIn = true
|
||||
return true
|
||||
}
|
||||
|
||||
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
|
||||
query := m.GetSignedInUserQuery{}
|
||||
if setting.AuthProxyHeaderProperty == "username" {
|
||||
query.Login = headerVal
|
||||
} else if setting.AuthProxyHeaderProperty == "email" {
|
||||
query.Email = headerVal
|
||||
} else {
|
||||
panic("Auth proxy header property invalid")
|
||||
}
|
||||
return &query
|
||||
}
|
||||
|
||||
func getCreateUserCommandForProxyAuth(headerVal string) *m.CreateUserCommand {
|
||||
cmd := m.CreateUserCommand{}
|
||||
if setting.AuthProxyHeaderProperty == "username" {
|
||||
cmd.Login = headerVal
|
||||
cmd.Email = headerVal
|
||||
} else if setting.AuthProxyHeaderProperty == "email" {
|
||||
cmd.Email = headerVal
|
||||
cmd.Login = headerVal
|
||||
} else {
|
||||
panic("Auth proxy header property invalid")
|
||||
}
|
||||
return &cmd
|
||||
}
|
35
pkg/middleware/auth_test.go
Normal file
35
pkg/middleware/auth_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestMiddlewareAuth(t *testing.T) {
|
||||
|
||||
Convey("Given the grafana middleware", t, func() {
|
||||
reqSignIn := Auth(&AuthOptions{ReqSignedIn: true})
|
||||
|
||||
middlewareScenario("ReqSignIn true and unauthenticated request", func(sc *scenarioContext) {
|
||||
sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/secure").exec()
|
||||
|
||||
Convey("Should redirect to login", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 302)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario("ReqSignIn true and unauthenticated API request", func(sc *scenarioContext) {
|
||||
sc.m.Get("/api/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/api/secure").exec()
|
||||
|
||||
Convey("Should return 401", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 401)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
@ -40,6 +41,8 @@ func GetContextHandler() macaron.Handler {
|
||||
// then look for api key in session (special case for render calls via api)
|
||||
// then test if anonymous access is enabled
|
||||
if initContextWithApiKey(ctx) ||
|
||||
initContextWithBasicAuth(ctx) ||
|
||||
initContextWithAuthProxy(ctx) ||
|
||||
initContextWithUserSessionCookie(ctx) ||
|
||||
initContextWithApiKeyFromSession(ctx) ||
|
||||
initContextWithAnonymousUser(ctx) {
|
||||
@ -83,6 +86,7 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
|
||||
|
||||
query := m.GetSignedInUserQuery{UserId: userId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
log.Error(3, "Failed to get user with id %v", userId)
|
||||
return false
|
||||
} else {
|
||||
ctx.SignedInUser = query.Result
|
||||
@ -126,6 +130,47 @@ func initContextWithApiKey(ctx *Context) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func initContextWithBasicAuth(ctx *Context) bool {
|
||||
if !setting.BasicAuthEnabled {
|
||||
return false
|
||||
}
|
||||
|
||||
header := ctx.Req.Header.Get("Authorization")
|
||||
if header == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
username, password, err := util.DecodeBasicAuthHeader(header)
|
||||
if err != nil {
|
||||
ctx.JsonApiErr(401, "Invalid Basic Auth Header", err)
|
||||
return true
|
||||
}
|
||||
|
||||
loginQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
|
||||
if err := bus.Dispatch(&loginQuery); err != nil {
|
||||
ctx.JsonApiErr(401, "Basic auth failed", err)
|
||||
return true
|
||||
}
|
||||
|
||||
user := loginQuery.Result
|
||||
|
||||
// validate password
|
||||
if util.EncodePassword(password, user.Salt) != user.Password {
|
||||
ctx.JsonApiErr(401, "Invalid username or password", nil)
|
||||
return true
|
||||
}
|
||||
|
||||
query := m.GetSignedInUserQuery{UserId: user.Id}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
ctx.JsonApiErr(401, "Authentication error", err)
|
||||
return true
|
||||
} else {
|
||||
ctx.SignedInUser = query.Result
|
||||
ctx.IsSignedIn = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// special case for panel render calls with api key
|
||||
func initContextWithApiKeyFromSession(ctx *Context) bool {
|
||||
keyId := ctx.Session.Get(SESS_KEY_APIKEY)
|
||||
@ -195,10 +240,10 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
|
||||
|
||||
switch status {
|
||||
case 404:
|
||||
resp["message"] = "Not Found"
|
||||
metrics.M_Api_Status_500.Inc(1)
|
||||
case 500:
|
||||
metrics.M_Api_Status_404.Inc(1)
|
||||
resp["message"] = "Not Found"
|
||||
case 500:
|
||||
metrics.M_Api_Status_500.Inc(1)
|
||||
resp["message"] = "Internal Server Error"
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user