Merge branch 'master' into cli_colors

pull/4397/head
bergquist 9 years ago
commit 1a11f1e8c6
  1. 14
      Godeps/Godeps.json
  2. 7
      Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore
  3. 45
      Godeps/_workspace/src/github.com/bmizerany/assert/README.md
  4. 76
      Godeps/_workspace/src/github.com/bmizerany/assert/assert.go
  5. 15
      Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go
  6. 5
      Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go
  7. 13
      Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go
  8. 168
      Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go
  9. 4
      Godeps/_workspace/src/github.com/kr/pretty/.gitignore
  10. 21
      Godeps/_workspace/src/github.com/kr/pretty/License
  11. 9
      Godeps/_workspace/src/github.com/kr/pretty/Readme
  12. 158
      Godeps/_workspace/src/github.com/kr/pretty/diff.go
  13. 74
      Godeps/_workspace/src/github.com/kr/pretty/diff_test.go
  14. 20
      Godeps/_workspace/src/github.com/kr/pretty/example_test.go
  15. 337
      Godeps/_workspace/src/github.com/kr/pretty/formatter.go
  16. 261
      Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go
  17. 98
      Godeps/_workspace/src/github.com/kr/pretty/pretty.go
  18. 41
      Godeps/_workspace/src/github.com/kr/pretty/zero.go
  19. 19
      Godeps/_workspace/src/github.com/kr/text/License
  20. 3
      Godeps/_workspace/src/github.com/kr/text/Readme
  21. 5
      Godeps/_workspace/src/github.com/kr/text/colwriter/Readme
  22. 147
      Godeps/_workspace/src/github.com/kr/text/colwriter/column.go
  23. 90
      Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go
  24. 3
      Godeps/_workspace/src/github.com/kr/text/doc.go
  25. 74
      Godeps/_workspace/src/github.com/kr/text/indent.go
  26. 119
      Godeps/_workspace/src/github.com/kr/text/indent_test.go
  27. 9
      Godeps/_workspace/src/github.com/kr/text/mc/Readme
  28. 62
      Godeps/_workspace/src/github.com/kr/text/mc/mc.go
  29. 86
      Godeps/_workspace/src/github.com/kr/text/wrap.go
  30. 62
      Godeps/_workspace/src/github.com/kr/text/wrap_test.go
  31. 2
      docs/Makefile
  32. 2
      docs/VERSION
  33. 6
      docs/sources/plugins/installation.md
  34. 6
      pkg/api/api.go
  35. 7
      pkg/api/cloudwatch/metrics.go
  36. 2
      pkg/api/dashboard_snapshot.go
  37. 35
      pkg/api/dtos/models.go
  38. 24
      pkg/api/dtos/plugins.go
  39. 2
      pkg/api/frontendsettings.go
  40. 32
      pkg/api/plugins.go
  41. 7
      pkg/cmd/grafana-cli/commands/install_command.go
  42. 817
      pkg/components/dynmap/dynmap.go
  43. 313
      pkg/components/dynmap/dynmap_test.go
  44. 468
      pkg/components/simplejson/simplejson.go
  45. 89
      pkg/components/simplejson/simplejson_go11.go
  46. 248
      pkg/components/simplejson/simplejson_test.go
  47. 14
      pkg/models/dashboard_snapshot.go
  48. 52
      pkg/models/dashboards.go
  49. 8
      pkg/models/dashboards_test.go
  50. 56
      pkg/models/datasource.go
  51. 172
      pkg/plugins/dashboard_importer.go
  52. 94
      pkg/plugins/dashboard_importer_test.go
  53. 57
      pkg/plugins/dashboard_installer.go
  54. 8
      pkg/plugins/dashboards.go
  55. 2
      pkg/plugins/dashboards_test.go
  56. 27
      pkg/plugins/frontend_plugin.go
  57. 21
      pkg/plugins/models.go
  58. 29
      pkg/plugins/plugins.go
  59. 8
      pkg/services/search/json_index.go
  60. 4
      pkg/services/sqlstore/dashboard.go
  61. 7
      pkg/services/sqlstore/dashboard_snapshot_test.go
  62. 17
      pkg/services/sqlstore/dashboard_test.go
  63. 74
      public/app/core/components/layout_selector/layout_selector.ts
  64. 2
      public/app/core/core.ts
  65. 2
      public/app/core/utils/kbn.js
  66. 4
      public/app/features/dashboard/dashnav/dashnav.ts
  67. 14
      public/app/features/dashboard/impression_store.ts
  68. 3
      public/app/features/plugins/ds_edit_ctrl.ts
  69. 8
      public/app/features/plugins/import_list/import_list.html
  70. 19
      public/app/features/plugins/import_list/import_list.ts
  71. 16
      public/app/features/plugins/partials/ds_edit.html
  72. 24
      public/app/features/plugins/partials/plugin_edit.html
  73. 75
      public/app/features/plugins/partials/plugin_list.html
  74. 34
      public/app/features/plugins/plugin_edit_ctrl.ts
  75. 21
      public/app/features/plugins/plugin_list_ctrl.ts
  76. 2
      public/app/features/plugins/plugin_page_ctrl.ts
  77. 13
      public/app/features/templating/templateSrv.js
  78. 2
      public/app/plugins/datasource/cloudwatch/datasource.js
  79. BIN
      public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png
  80. 13
      public/app/plugins/datasource/cloudwatch/plugin.json
  81. 2
      public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html
  82. 2
      public/app/plugins/datasource/elasticsearch/plugin.json
  83. 176
      public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json
  84. 21
      public/app/plugins/datasource/graphite/dashboards/carbon_stats.json
  85. BIN
      public/app/plugins/datasource/graphite/img/graphite_logo.png
  86. 17
      public/app/plugins/datasource/graphite/plugin.json
  87. 15
      public/app/plugins/datasource/influxdb/img/influxdb_logo.svg
  88. 13
      public/app/plugins/datasource/influxdb/plugin.json
  89. BIN
      public/app/plugins/datasource/opentsdb/img/opentsdb_logo.png
  90. 13
      public/app/plugins/datasource/opentsdb/plugin.json
  91. 577
      public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json
  92. 19
      public/app/plugins/datasource/prometheus/img/prometheus_logo.svg
  93. 17
      public/app/plugins/datasource/prometheus/plugin.json
  94. 9
      public/app/plugins/panel/dashlist/module.ts
  95. 9
      public/app/plugins/panel/dashlist/plugin.json
  96. 9
      public/app/plugins/panel/graph/plugin.json
  97. 9
      public/app/plugins/panel/singlestat/plugin.json
  98. 9
      public/app/plugins/panel/table/plugin.json
  99. 9
      public/app/plugins/panel/text/plugin.json
  100. BIN
      public/fonts/opensans/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2
  101. Some files were not shown because too many files have changed in this diff Show More

14
Godeps/Godeps.json generated vendored

@ -124,6 +124,11 @@
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/bmizerany/assert",
"Comment": "release.r60-6-ge17e998",
"Rev": "e17e99893cb6509f428e1728281c2ad60a6b31e3"
},
{
"ImportPath": "github.com/bradfitz/gomemcache/memcache",
"Comment": "release.r60-40-g72a6864",
@ -237,6 +242,15 @@
"ImportPath": "github.com/klauspost/crc32",
"Rev": "6834731faf32e62a2dd809d99fb24d1e4ae5a92d"
},
{
"ImportPath": "github.com/kr/pretty",
"Comment": "go.weekly.2011-12-22-27-ge6ac2fc",
"Rev": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb"
},
{
"ImportPath": "github.com/kr/text",
"Rev": "bb797dc4fb8320488f47bf11de07a733d7233e1f"
},
{
"ImportPath": "github.com/lib/pq",
"Comment": "go1.0-cutoff-13-g19eeca3",

@ -0,0 +1,7 @@
_go_.*
_gotest_.*
_obj
_test
_testmain.go
*.out
*.[568]

@ -0,0 +1,45 @@
# Assert (c) Blake Mizerany and Keith Rarick -- MIT LICENCE
## Assertions for Go tests
## Install
$ go get github.com/bmizerany/assert
## Use
**point.go**
package point
type Point struct {
x, y int
}
**point_test.go**
package point
import (
"testing"
"github.com/bmizerany/assert"
)
func TestAsserts(t *testing.T) {
p1 := Point{1, 1}
p2 := Point{2, 1}
assert.Equal(t, p1, p2)
}
**output**
$ go test
--- FAIL: TestAsserts (0.00 seconds)
assert.go:15: /Users/flavio.barbosa/dev/stewie/src/point_test.go:12
assert.go:24: ! X: 1 != 2
FAIL
## Docs
http://github.com/bmizerany/assert

@ -0,0 +1,76 @@
package assert
// Testing helpers for doozer.
import (
"github.com/kr/pretty"
"reflect"
"testing"
"runtime"
"fmt"
)
func assert(t *testing.T, result bool, f func(), cd int) {
if !result {
_, file, line, _ := runtime.Caller(cd + 1)
t.Errorf("%s:%d", file, line)
f()
t.FailNow()
}
}
func equal(t *testing.T, exp, got interface{}, cd int, args ...interface{}) {
fn := func() {
for _, desc := range pretty.Diff(exp, got) {
t.Error("!", desc)
}
if len(args) > 0 {
t.Error("!", " -", fmt.Sprint(args...))
}
}
result := reflect.DeepEqual(exp, got)
assert(t, result, fn, cd+1)
}
func tt(t *testing.T, result bool, cd int, args ...interface{}) {
fn := func() {
t.Errorf("! Failure")
if len(args) > 0 {
t.Error("!", " -", fmt.Sprint(args...))
}
}
assert(t, result, fn, cd+1)
}
func T(t *testing.T, result bool, args ...interface{}) {
tt(t, result, 1, args...)
}
func Tf(t *testing.T, result bool, format string, args ...interface{}) {
tt(t, result, 1, fmt.Sprintf(format, args...))
}
func Equal(t *testing.T, exp, got interface{}, args ...interface{}) {
equal(t, exp, got, 1, args...)
}
func Equalf(t *testing.T, exp, got interface{}, format string, args ...interface{}) {
equal(t, exp, got, 1, fmt.Sprintf(format, args...))
}
func NotEqual(t *testing.T, exp, got interface{}, args ...interface{}) {
fn := func() {
t.Errorf("! Unexpected: <%#v>", exp)
if len(args) > 0 {
t.Error("!", " -", fmt.Sprint(args...))
}
}
result := !reflect.DeepEqual(exp, got)
assert(t, result, fn, 1)
}
func Panic(t *testing.T, err interface{}, fn func()) {
defer func() {
equal(t, err, recover(), 3)
}()
fn()
}

@ -0,0 +1,15 @@
package assert
import (
"testing"
)
func TestLineNumbers(t *testing.T) {
Equal(t, "foo", "foo", "msg!")
//Equal(t, "foo", "bar", "this should blow up")
}
func TestNotEqual(t *testing.T) {
NotEqual(t, "foo", "bar", "msg!")
//NotEqual(t, "foo", "foo", "this should blow up")
}

@ -0,0 +1,5 @@
package point
type Point struct {
X, Y int
}

@ -0,0 +1,13 @@
package point
import (
"testing"
"assert"
)
func TestAsserts(t *testing.T) {
p1 := Point{1, 1}
p2 := Point{2, 1}
assert.Equal(t, p1, p2)
}

@ -1,168 +0,0 @@
// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
//
// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
//
// | Go type | JavaScript type | Conversions back to interface{} |
// | --------------------- | --------------------- | ------------------------------- |
// | bool | Boolean | bool |
// | integers and floats | Number | float64 |
// | string | String | string |
// | []int8 | Int8Array | []int8 |
// | []int16 | Int16Array | []int16 |
// | []int32, []int | Int32Array | []int |
// | []uint8 | Uint8Array | []uint8 |
// | []uint16 | Uint16Array | []uint16 |
// | []uint32, []uint | Uint32Array | []uint |
// | []float32 | Float32Array | []float32 |
// | []float64 | Float64Array | []float64 |
// | all other slices | Array | []interface{} |
// | arrays | see slice type | see slice type |
// | functions | Function | func(...interface{}) *js.Object |
// | time.Time | Date | time.Time |
// | - | instanceof Node | *js.Object |
// | maps, structs | instanceof Object | map[string]interface{} |
//
// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa.
package js
// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key.
type Object struct{ object *Object }
// Get returns the object's property with the given key.
func (o *Object) Get(key string) *Object { return o.object.Get(key) }
// Set assigns the value to the object's property with the given key.
func (o *Object) Set(key string, value interface{}) { o.object.Set(key, value) }
// Delete removes the object's property with the given key.
func (o *Object) Delete(key string) { o.object.Delete(key) }
// Length returns the object's "length" property, converted to int.
func (o *Object) Length() int { return o.object.Length() }
// Index returns the i'th element of an array.
func (o *Object) Index(i int) *Object { return o.object.Index(i) }
// SetIndex sets the i'th element of an array.
func (o *Object) SetIndex(i int, value interface{}) { o.object.SetIndex(i, value) }
// Call calls the object's method with the given name.
func (o *Object) Call(name string, args ...interface{}) *Object { return o.object.Call(name, args...) }
// Invoke calls the object itself. This will fail if it is not a function.
func (o *Object) Invoke(args ...interface{}) *Object { return o.object.Invoke(args...) }
// New creates a new instance of this type object. This will fail if it not a function (constructor).
func (o *Object) New(args ...interface{}) *Object { return o.object.New(args...) }
// Bool returns the object converted to bool according to JavaScript type conversions.
func (o *Object) Bool() bool { return o.object.Bool() }
// String returns the object converted to string according to JavaScript type conversions.
func (o *Object) String() string { return o.object.String() }
// Int returns the object converted to int according to JavaScript type conversions (parseInt).
func (o *Object) Int() int { return o.object.Int() }
// Int64 returns the object converted to int64 according to JavaScript type conversions (parseInt).
func (o *Object) Int64() int64 { return o.object.Int64() }
// Uint64 returns the object converted to uint64 according to JavaScript type conversions (parseInt).
func (o *Object) Uint64() uint64 { return o.object.Uint64() }
// Float returns the object converted to float64 according to JavaScript type conversions (parseFloat).
func (o *Object) Float() float64 { return o.object.Float() }
// Interface returns the object converted to interface{}. See GopherJS' README for details.
func (o *Object) Interface() interface{} { return o.object.Interface() }
// Unsafe returns the object as an uintptr, which can be converted via unsafe.Pointer. Not intended for public use.
func (o *Object) Unsafe() uintptr { return o.object.Unsafe() }
// Error encapsulates JavaScript errors. Those are turned into a Go panic and may be recovered, giving an *Error that holds the JavaScript error object.
type Error struct {
*Object
}
// Error returns the message of the encapsulated JavaScript error object.
func (err *Error) Error() string {
return "JavaScript error: " + err.Get("message").String()
}
// Stack returns the stack property of the encapsulated JavaScript error object.
func (err *Error) Stack() string {
return err.Get("stack").String()
}
// Global gives JavaScript's global object ("window" for browsers and "GLOBAL" for Node.js).
var Global *Object
// Module gives the value of the "module" variable set by Node.js. Hint: Set a module export with 'js.Module.Get("exports").Set("exportName", ...)'.
var Module *Object
// Undefined gives the JavaScript value "undefined".
var Undefined *Object
// Debugger gets compiled to JavaScript's "debugger;" statement.
func Debugger() {}
// InternalObject returns the internal JavaScript object that represents i. Not intended for public use.
func InternalObject(i interface{}) *Object {
return nil
}
// MakeFunc wraps a function and gives access to the values of JavaScript's "this" and "arguments" keywords.
func MakeFunc(func(this *Object, arguments []*Object) interface{}) *Object {
return nil
}
// Keys returns the keys of the given JavaScript object.
func Keys(o *Object) []string {
if o == nil || o == Undefined {
return nil
}
a := Global.Get("Object").Call("keys", o)
s := make([]string, a.Length())
for i := 0; i < a.Length(); i++ {
s[i] = a.Index(i).String()
}
return s
}
// MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript.
func MakeWrapper(i interface{}) *Object {
v := InternalObject(i)
o := Global.Get("Object").New()
o.Set("__internal_object__", v)
methods := v.Get("constructor").Get("methods")
for i := 0; i < methods.Length(); i++ {
m := methods.Index(i)
if m.Get("pkg").String() != "" { // not exported
continue
}
o.Set(m.Get("name").String(), func(args ...*Object) *Object {
return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true).Call("apply", v, args)
})
}
return o
}
// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice.
func NewArrayBuffer(b []byte) *Object {
slice := InternalObject(b)
offset := slice.Get("$offset").Int()
length := slice.Get("$length").Int()
return slice.Get("$array").Get("buffer").Call("slice", offset, offset+length)
}
// M is a simple map type. It is intended as a shorthand for JavaScript objects (before conversion).
type M map[string]interface{}
// S is a simple slice type. It is intended as a shorthand for JavaScript arrays (before conversion).
type S []interface{}
func init() {
// avoid dead code elimination
e := Error{}
_ = e
}

@ -0,0 +1,4 @@
[568].out
_go*
_test*
_obj

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,9 @@
package pretty
import "github.com/kr/pretty"
Package pretty provides pretty-printing for Go values.
Documentation
http://godoc.org/github.com/kr/pretty

@ -0,0 +1,158 @@
package pretty
import (
"fmt"
"io"
"reflect"
)
type sbuf []string
func (s *sbuf) Write(b []byte) (int, error) {
*s = append(*s, string(b))
return len(b), nil
}
// Diff returns a slice where each element describes
// a difference between a and b.
func Diff(a, b interface{}) (desc []string) {
Fdiff((*sbuf)(&desc), a, b)
return desc
}
// Fdiff writes to w a description of the differences between a and b.
func Fdiff(w io.Writer, a, b interface{}) {
diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
}
type diffWriter struct {
w io.Writer
l string // label
}
func (w diffWriter) printf(f string, a ...interface{}) {
var l string
if w.l != "" {
l = w.l + ": "
}
fmt.Fprintf(w.w, l+f, a...)
}
func (w diffWriter) diff(av, bv reflect.Value) {
if !av.IsValid() && bv.IsValid() {
w.printf("nil != %#v", bv.Interface())
return
}
if av.IsValid() && !bv.IsValid() {
w.printf("%#v != nil", av.Interface())
return
}
if !av.IsValid() && !bv.IsValid() {
return
}
at := av.Type()
bt := bv.Type()
if at != bt {
w.printf("%v != %v", at, bt)
return
}
// numeric types, including bool
if at.Kind() < reflect.Array {
a, b := av.Interface(), bv.Interface()
if a != b {
w.printf("%#v != %#v", a, b)
}
return
}
switch at.Kind() {
case reflect.String:
a, b := av.Interface(), bv.Interface()
if a != b {
w.printf("%q != %q", a, b)
}
case reflect.Ptr:
switch {
case av.IsNil() && !bv.IsNil():
w.printf("nil != %v", bv.Interface())
case !av.IsNil() && bv.IsNil():
w.printf("%v != nil", av.Interface())
case !av.IsNil() && !bv.IsNil():
w.diff(av.Elem(), bv.Elem())
}
case reflect.Struct:
for i := 0; i < av.NumField(); i++ {
w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
}
case reflect.Slice:
lenA := av.Len()
lenB := bv.Len()
if lenA != lenB {
w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
break
}
for i := 0; i < lenA; i++ {
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
}
case reflect.Map:
ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
for _, k := range ak {
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
w.printf("%q != (missing)", av.MapIndex(k))
}
for _, k := range both {
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
w.diff(av.MapIndex(k), bv.MapIndex(k))
}
for _, k := range bk {
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
w.printf("(missing) != %q", bv.MapIndex(k))
}
case reflect.Interface:
w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
default:
if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
}
}
}
func (d diffWriter) relabel(name string) (d1 diffWriter) {
d1 = d
if d.l != "" && name[0] != '[' {
d1.l += "."
}
d1.l += name
return d1
}
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
for _, av := range a {
inBoth := false
for _, bv := range b {
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
inBoth = true
both = append(both, av)
break
}
}
if !inBoth {
ak = append(ak, av)
}
}
for _, bv := range b {
inBoth := false
for _, av := range a {
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
inBoth = true
break
}
}
if !inBoth {
bk = append(bk, bv)
}
}
return
}

@ -0,0 +1,74 @@
package pretty
import (
"testing"
)
type difftest struct {
a interface{}
b interface{}
exp []string
}
type S struct {
A int
S *S
I interface{}
C []int
}
var diffs = []difftest{
{a: nil, b: nil},
{a: S{A: 1}, b: S{A: 1}},
{0, "", []string{`int != string`}},
{0, 1, []string{`0 != 1`}},
{S{}, new(S), []string{`pretty.S != *pretty.S`}},
{"a", "b", []string{`"a" != "b"`}},
{S{}, S{A: 1}, []string{`A: 0 != 1`}},
{new(S), &S{A: 1}, []string{`A: 0 != 1`}},
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
{S{}, S{I: 0}, []string{`I: nil != 0`}},
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
{S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
{S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
{S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}},
{S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 <nil> <nil> []}`}},
}
func TestDiff(t *testing.T) {
for _, tt := range diffs {
got := Diff(tt.a, tt.b)
eq := len(got) == len(tt.exp)
if eq {
for i := range got {
eq = eq && got[i] == tt.exp[i]
}
}
if !eq {
t.Errorf("diffing % #v", tt.a)
t.Errorf("with % #v", tt.b)
diffdiff(t, got, tt.exp)
continue
}
}
}
func diffdiff(t *testing.T, got, exp []string) {
minus(t, "unexpected:", got, exp)
minus(t, "missing:", exp, got)
}
func minus(t *testing.T, s string, a, b []string) {
var i, j int
for i = 0; i < len(a); i++ {
for j = 0; j < len(b); j++ {
if a[i] == b[j] {
break
}
}
if j == len(b) {
t.Error(s, a[i])
}
}
}

@ -0,0 +1,20 @@
package pretty_test
import (
"fmt"
"github.com/kr/pretty"
)
func Example() {
type myType struct {
a, b int
}
var x = []myType{{1, 2}, {3, 4}, {5, 6}}
fmt.Printf("%# v", pretty.Formatter(x))
// output:
// []pretty_test.myType{
// {a:1, b:2},
// {a:3, b:4},
// {a:5, b:6},
// }
}

@ -0,0 +1,337 @@
package pretty
import (
"fmt"
"io"
"reflect"
"strconv"
"text/tabwriter"
"github.com/kr/text"
)
const (
limit = 50
)
type formatter struct {
x interface{}
force bool
quote bool
}
// Formatter makes a wrapper, f, that will format x as go source with line
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
// "#" and " " (space) flags are set, for example:
//
// fmt.Sprintf("%# v", Formatter(x))
//
// If one of these two flags is not set, or any other verb is used, f will
// format x according to the usual rules of package fmt.
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
func Formatter(x interface{}) (f fmt.Formatter) {
return formatter{x: x, quote: true}
}
func (fo formatter) String() string {
return fmt.Sprint(fo.x) // unwrap it
}
func (fo formatter) passThrough(f fmt.State, c rune) {
s := "%"
for i := 0; i < 128; i++ {
if f.Flag(i) {
s += string(i)
}
}
if w, ok := f.Width(); ok {
s += fmt.Sprintf("%d", w)
}
if p, ok := f.Precision(); ok {
s += fmt.Sprintf(".%d", p)
}
s += string(c)
fmt.Fprintf(f, s, fo.x)
}
func (fo formatter) Format(f fmt.State, c rune) {
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
w.Flush()
return
}
fo.passThrough(f, c)
}
type printer struct {
io.Writer
tw *tabwriter.Writer
visited map[visit]int
depth int
}
func (p *printer) indent() *printer {
q := *p
q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
return &q
}
func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
if showType {
io.WriteString(p, v.Type().String())
fmt.Fprintf(p, "(%#v)", x)
} else {
fmt.Fprintf(p, "%#v", x)
}
}
// printValue must keep track of already-printed pointer values to avoid
// infinite recursion.
type visit struct {
v uintptr
typ reflect.Type
}
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
if p.depth > 10 {
io.WriteString(p, "!%v(DEPTH EXCEEDED)")
return
}
switch v.Kind() {
case reflect.Bool:
p.printInline(v, v.Bool(), showType)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p.printInline(v, v.Int(), showType)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p.printInline(v, v.Uint(), showType)
case reflect.Float32, reflect.Float64:
p.printInline(v, v.Float(), showType)
case reflect.Complex64, reflect.Complex128:
fmt.Fprintf(p, "%#v", v.Complex())
case reflect.String:
p.fmtString(v.String(), quote)
case reflect.Map:
t := v.Type()
if showType {
io.WriteString(p, t.String())
}
writeByte(p, '{')
if nonzero(v) {
expand := !canInline(v.Type())
pp := p
if expand {
writeByte(p, '\n')
pp = p.indent()
}
keys := v.MapKeys()
for i := 0; i < v.Len(); i++ {
showTypeInStruct := true
k := keys[i]
mv := v.MapIndex(k)
pp.printValue(k, false, true)
writeByte(pp, ':')
if expand {
writeByte(pp, '\t')
}
showTypeInStruct = t.Elem().Kind() == reflect.Interface
pp.printValue(mv, showTypeInStruct, true)
if expand {
io.WriteString(pp, ",\n")
} else if i < v.Len()-1 {
io.WriteString(pp, ", ")
}
}
if expand {
pp.tw.Flush()
}
}
writeByte(p, '}')
case reflect.Struct:
t := v.Type()
if v.CanAddr() {
addr := v.UnsafeAddr()
vis := visit{addr, t}
if vd, ok := p.visited[vis]; ok && vd < p.depth {
p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
break // don't print v again
}
p.visited[vis] = p.depth
}
if showType {
io.WriteString(p, t.String())
}
writeByte(p, '{')
if nonzero(v) {
expand := !canInline(v.Type())
pp := p
if expand {
writeByte(p, '\n')
pp = p.indent()
}
for i := 0; i < v.NumField(); i++ {
showTypeInStruct := true
if f := t.Field(i); f.Name != "" {
io.WriteString(pp, f.Name)
writeByte(pp, ':')
if expand {
writeByte(pp, '\t')
}
showTypeInStruct = labelType(f.Type)
}
pp.printValue(getField(v, i), showTypeInStruct, true)
if expand {
io.WriteString(pp, ",\n")
} else if i < v.NumField()-1 {
io.WriteString(pp, ", ")
}
}
if expand {
pp.tw.Flush()
}
}
writeByte(p, '}')
case reflect.Interface:
switch e := v.Elem(); {
case e.Kind() == reflect.Invalid:
io.WriteString(p, "nil")
case e.IsValid():
pp := *p
pp.depth++
pp.printValue(e, showType, true)
default:
io.WriteString(p, v.Type().String())
io.WriteString(p, "(nil)")
}
case reflect.Array, reflect.Slice:
t := v.Type()
if showType {
io.WriteString(p, t.String())
}
if v.Kind() == reflect.Slice && v.IsNil() && showType {
io.WriteString(p, "(nil)")
break
}
if v.Kind() == reflect.Slice && v.IsNil() {
io.WriteString(p, "nil")
break
}
writeByte(p, '{')
expand := !canInline(v.Type())
pp := p
if expand {
writeByte(p, '\n')
pp = p.indent()
}
for i := 0; i < v.Len(); i++ {
showTypeInSlice := t.Elem().Kind() == reflect.Interface
pp.printValue(v.Index(i), showTypeInSlice, true)
if expand {
io.WriteString(pp, ",\n")
} else if i < v.Len()-1 {
io.WriteString(pp, ", ")
}
}
if expand {
pp.tw.Flush()
}
writeByte(p, '}')
case reflect.Ptr:
e := v.Elem()
if !e.IsValid() {
writeByte(p, '(')
io.WriteString(p, v.Type().String())
io.WriteString(p, ")(nil)")
} else {
pp := *p
pp.depth++
writeByte(pp, '&')
pp.printValue(e, true, true)
}
case reflect.Chan:
x := v.Pointer()
if showType {
writeByte(p, '(')
io.WriteString(p, v.Type().String())
fmt.Fprintf(p, ")(%#v)", x)
} else {
fmt.Fprintf(p, "%#v", x)
}
case reflect.Func:
io.WriteString(p, v.Type().String())
io.WriteString(p, " {...}")
case reflect.UnsafePointer:
p.printInline(v, v.Pointer(), showType)
case reflect.Invalid:
io.WriteString(p, "nil")
}
}
func canInline(t reflect.Type) bool {
switch t.Kind() {
case reflect.Map:
return !canExpand(t.Elem())
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
if canExpand(t.Field(i).Type) {
return false
}
}
return true
case reflect.Interface:
return false
case reflect.Array, reflect.Slice:
return !canExpand(t.Elem())
case reflect.Ptr:
return false
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
return false
}
return true
}
func canExpand(t reflect.Type) bool {
switch t.Kind() {
case reflect.Map, reflect.Struct,
reflect.Interface, reflect.Array, reflect.Slice,
reflect.Ptr:
return true
}
return false
}
func labelType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Interface, reflect.Struct:
return true
}
return false
}
func (p *printer) fmtString(s string, quote bool) {
if quote {
s = strconv.Quote(s)
}
io.WriteString(p, s)
}
func tryDeepEqual(a, b interface{}) bool {
defer func() { recover() }()
return reflect.DeepEqual(a, b)
}
func writeByte(w io.Writer, b byte) {
w.Write([]byte{b})
}
func getField(v reflect.Value, i int) reflect.Value {
val := v.Field(i)
if val.Kind() == reflect.Interface && !val.IsNil() {
val = val.Elem()
}
return val
}

@ -0,0 +1,261 @@
package pretty
import (
"fmt"
"io"
"strings"
"testing"
"unsafe"
)
type test struct {
v interface{}
s string
}
type LongStructTypeName struct {
longFieldName interface{}
otherLongFieldName interface{}
}
type SA struct {
t *T
v T
}
type T struct {
x, y int
}
type F int
func (f F) Format(s fmt.State, c rune) {
fmt.Fprintf(s, "F(%d)", int(f))
}
var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var gosyntax = []test{
{nil, `nil`},
{"", `""`},
{"a", `"a"`},
{1, "int(1)"},
{1.0, "float64(1)"},
{[]int(nil), "[]int(nil)"},
{[0]int{}, "[0]int{}"},
{complex(1, 0), "(1+0i)"},
//{make(chan int), "(chan int)(0x1234)"},
{unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))},
{func(int) {}, "func(int) {...}"},
{map[int]int{1: 1}, "map[int]int{1:1}"},
{int32(1), "int32(1)"},
{io.EOF, `&errors.errorString{s:"EOF"}`},
{[]string{"a"}, `[]string{"a"}`},
{
[]string{long},
`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
},
{F(5), "pretty.F(5)"},
{
SA{&T{1, 2}, T{3, 4}},
`pretty.SA{
t: &pretty.T{x:1, y:2},
v: pretty.T{x:3, y:4},
}`,
},
{
map[int][]byte{1: {}},
`map[int][]uint8{
1: {},
}`,
},
{
map[int]T{1: {}},
`map[int]pretty.T{
1: {},
}`,
},
{
long,
`"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`,
},
{
LongStructTypeName{
longFieldName: LongStructTypeName{},
otherLongFieldName: long,
},
`pretty.LongStructTypeName{
longFieldName: pretty.LongStructTypeName{},
otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
}`,
},
{
&LongStructTypeName{
longFieldName: &LongStructTypeName{},
otherLongFieldName: (*LongStructTypeName)(nil),
},
`&pretty.LongStructTypeName{
longFieldName: &pretty.LongStructTypeName{},
otherLongFieldName: (*pretty.LongStructTypeName)(nil),
}`,
},
{
[]LongStructTypeName{
{nil, nil},
{3, 3},
{long, nil},
},
`[]pretty.LongStructTypeName{
{},
{
longFieldName: int(3),
otherLongFieldName: int(3),
},
{
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
otherLongFieldName: nil,
},
}`,
},
{
[]interface{}{
LongStructTypeName{nil, nil},
[]byte{1, 2, 3},
T{3, 4},
LongStructTypeName{long, nil},
},
`[]interface {}{
pretty.LongStructTypeName{},
[]uint8{0x1, 0x2, 0x3},
pretty.T{x:3, y:4},
pretty.LongStructTypeName{
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
otherLongFieldName: nil,
},
}`,
},
}
func TestGoSyntax(t *testing.T) {
for _, tt := range gosyntax {
s := fmt.Sprintf("%# v", Formatter(tt.v))
if tt.s != s {
t.Errorf("expected %q", tt.s)
t.Errorf("got %q", s)
t.Errorf("expraw\n%s", tt.s)
t.Errorf("gotraw\n%s", s)
}
}
}
type I struct {
i int
R interface{}
}
func (i *I) I() *I { return i.R.(*I) }
func TestCycle(t *testing.T) {
type A struct{ *A }
v := &A{}
v.A = v
// panics from stack overflow without cycle detection
t.Logf("Example cycle:\n%# v", Formatter(v))
p := &A{}
s := fmt.Sprintf("%# v", Formatter([]*A{p, p}))
if strings.Contains(s, "CYCLIC") {
t.Errorf("Repeated address detected as cyclic reference:\n%s", s)
}
type R struct {
i int
*R
}
r := &R{
i: 1,
R: &R{
i: 2,
R: &R{
i: 3,
},
},
}
r.R.R.R = r
t.Logf("Example longer cycle:\n%# v", Formatter(r))
r = &R{
i: 1,
R: &R{
i: 2,
R: &R{
i: 3,
R: &R{
i: 4,
R: &R{
i: 5,
R: &R{
i: 6,
R: &R{
i: 7,
R: &R{
i: 8,
R: &R{
i: 9,
R: &R{
i: 10,
R: &R{
i: 11,
},
},
},
},
},
},
},
},
},
},
}
// here be pirates
r.R.R.R.R.R.R.R.R.R.R.R = r
t.Logf("Example very long cycle:\n%# v", Formatter(r))
i := &I{
i: 1,
R: &I{
i: 2,
R: &I{
i: 3,
R: &I{
i: 4,
R: &I{
i: 5,
R: &I{
i: 6,
R: &I{
i: 7,
R: &I{
i: 8,
R: &I{
i: 9,
R: &I{
i: 10,
R: &I{
i: 11,
},
},
},
},
},
},
},
},
},
},
}
iv := i.I().I().I().I().I().I().I().I().I().I()
*iv = *i
t.Logf("Example long interface cycle:\n%# v", Formatter(i))
}

@ -0,0 +1,98 @@
// Package pretty provides pretty-printing for Go values. This is
// useful during debugging, to avoid wrapping long output lines in
// the terminal.
//
// It provides a function, Formatter, that can be used with any
// function that accepts a format string. It also provides
// convenience wrappers for functions in packages fmt and log.
package pretty
import (
"fmt"
"io"
"log"
)
// Errorf is a convenience wrapper for fmt.Errorf.
//
// Calling Errorf(f, x, y) is equivalent to
// fmt.Errorf(f, Formatter(x), Formatter(y)).
func Errorf(format string, a ...interface{}) error {
return fmt.Errorf(format, wrap(a, false)...)
}
// Fprintf is a convenience wrapper for fmt.Fprintf.
//
// Calling Fprintf(w, f, x, y) is equivalent to
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
return fmt.Fprintf(w, format, wrap(a, false)...)
}
// Log is a convenience wrapper for log.Printf.
//
// Calling Log(x, y) is equivalent to
// log.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Log(a ...interface{}) {
log.Print(wrap(a, true)...)
}
// Logf is a convenience wrapper for log.Printf.
//
// Calling Logf(f, x, y) is equivalent to
// log.Printf(f, Formatter(x), Formatter(y)).
func Logf(format string, a ...interface{}) {
log.Printf(format, wrap(a, false)...)
}
// Logln is a convenience wrapper for log.Printf.
//
// Calling Logln(x, y) is equivalent to
// log.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Logln(a ...interface{}) {
log.Println(wrap(a, true)...)
}
// Print pretty-prints its operands and writes to standard output.
//
// Calling Print(x, y) is equivalent to
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Print(a ...interface{}) (n int, errno error) {
return fmt.Print(wrap(a, true)...)
}
// Printf is a convenience wrapper for fmt.Printf.
//
// Calling Printf(f, x, y) is equivalent to
// fmt.Printf(f, Formatter(x), Formatter(y)).
func Printf(format string, a ...interface{}) (n int, errno error) {
return fmt.Printf(format, wrap(a, false)...)
}
// Println pretty-prints its operands and writes to standard output.
//
// Calling Print(x, y) is equivalent to
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Println(a ...interface{}) (n int, errno error) {
return fmt.Println(wrap(a, true)...)
}
// Sprintf is a convenience wrapper for fmt.Sprintf.
//
// Calling Sprintf(f, x, y) is equivalent to
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, wrap(a, false)...)
}
func wrap(a []interface{}, force bool) []interface{} {
w := make([]interface{}, len(a))
for i, x := range a {
w[i] = formatter{x: x, force: force}
}
return w
}

@ -0,0 +1,41 @@
package pretty
import (
"reflect"
)
func nonzero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() != 0
case reflect.Float32, reflect.Float64:
return v.Float() != 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() != complex(0, 0)
case reflect.String:
return v.String() != ""
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if nonzero(getField(v, i)) {
return true
}
}
return false
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if nonzero(v.Index(i)) {
return true
}
}
return false
case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
return !v.IsNil()
case reflect.UnsafePointer:
return v.Pointer() != 0
}
return true
}

@ -0,0 +1,19 @@
Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,3 @@
This is a Go package for manipulating paragraphs of text.
See http://go.pkgdoc.org/github.com/kr/text for full documentation.

@ -0,0 +1,5 @@
Package colwriter provides a write filter that formats
input lines in multiple columns.
The package is a straightforward translation from
/src/cmd/draw/mc.c in Plan 9 from User Space.

@ -0,0 +1,147 @@
// Package colwriter provides a write filter that formats
// input lines in multiple columns.
//
// The package is a straightforward translation from
// /src/cmd/draw/mc.c in Plan 9 from User Space.
package colwriter
import (
"bytes"
"io"
"unicode/utf8"
)
const (
tab = 4
)
const (
// Print each input line ending in a colon ':' separately.
BreakOnColon uint = 1 << iota
)
// A Writer is a filter that arranges input lines in as many columns as will
// fit in its width. Tab '\t' chars in the input are translated to sequences
// of spaces ending at multiples of 4 positions.
//
// If BreakOnColon is set, each input line ending in a colon ':' is written
// separately.
//
// The Writer assumes that all Unicode code points have the same width; this
// may not be true in some fonts.
type Writer struct {
w io.Writer
buf []byte
width int
flag uint
}
// NewWriter allocates and initializes a new Writer writing to w.
// Parameter width controls the total number of characters on each line
// across all columns.
func NewWriter(w io.Writer, width int, flag uint) *Writer {
return &Writer{
w: w,
width: width,
flag: flag,
}
}
// Write writes p to the writer w. The only errors returned are ones
// encountered while writing to the underlying output stream.
func (w *Writer) Write(p []byte) (n int, err error) {
var linelen int
var lastWasColon bool
for i, c := range p {
w.buf = append(w.buf, c)
linelen++
if c == '\t' {
w.buf[len(w.buf)-1] = ' '
for linelen%tab != 0 {
w.buf = append(w.buf, ' ')
linelen++
}
}
if w.flag&BreakOnColon != 0 && c == ':' {
lastWasColon = true
} else if lastWasColon {
if c == '\n' {
pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'})
if pos < 0 {
pos = 0
}
line := w.buf[pos:]
w.buf = w.buf[:pos]
if err = w.columnate(); err != nil {
if len(line) < i {
return i - len(line), err
}
return 0, err
}
if n, err := w.w.Write(line); err != nil {
if r := len(line) - n; r < i {
return i - r, err
}
return 0, err
}
}
lastWasColon = false
}
if c == '\n' {
linelen = 0
}
}
return len(p), nil
}
// Flush should be called after the last call to Write to ensure that any data
// buffered in the Writer is written to output.
func (w *Writer) Flush() error {
return w.columnate()
}
func (w *Writer) columnate() error {
words := bytes.Split(w.buf, []byte{'\n'})
w.buf = nil
if len(words[len(words)-1]) == 0 {
words = words[:len(words)-1]
}
maxwidth := 0
for _, wd := range words {
if n := utf8.RuneCount(wd); n > maxwidth {
maxwidth = n
}
}
maxwidth++ // space char
wordsPerLine := w.width / maxwidth
if wordsPerLine <= 0 {
wordsPerLine = 1
}
nlines := (len(words) + wordsPerLine - 1) / wordsPerLine
for i := 0; i < nlines; i++ {
col := 0
endcol := 0
for j := i; j < len(words); j += nlines {
endcol += maxwidth
_, err := w.w.Write(words[j])
if err != nil {
return err
}
col += utf8.RuneCount(words[j])
if j+nlines < len(words) {
for col < endcol {
_, err := w.w.Write([]byte{' '})
if err != nil {
return err
}
col++
}
}
}
_, err := w.w.Write([]byte{'\n'})
if err != nil {
return err
}
}
return nil
}

@ -0,0 +1,90 @@
package colwriter
import (
"bytes"
"testing"
)
var src = `
.git
.gitignore
.godir
Procfile:
README.md
api.go
apps.go
auth.go
darwin.go
data.go
dyno.go:
env.go
git.go
help.go
hkdist
linux.go
ls.go
main.go
plugin.go
run.go
scale.go
ssh.go
tail.go
term
unix.go
update.go
version.go
windows.go
`[1:]
var tests = []struct {
wid int
flag uint
src string
want string
}{
{80, 0, "", ""},
{80, 0, src, `
.git README.md darwin.go git.go ls.go scale.go unix.go
.gitignore api.go data.go help.go main.go ssh.go update.go
.godir apps.go dyno.go: hkdist plugin.go tail.go version.go
Procfile: auth.go env.go linux.go run.go term windows.go
`[1:]},
{80, BreakOnColon, src, `
.git .gitignore .godir
Procfile:
README.md api.go apps.go auth.go darwin.go data.go
dyno.go:
env.go hkdist main.go scale.go term version.go
git.go linux.go plugin.go ssh.go unix.go windows.go
help.go ls.go run.go tail.go update.go
`[1:]},
{20, 0, `
Hello
Γειά σου
안녕
今日は
`[1:], `
Hello 안녕
Γειά σου 今日は
`[1:]},
}
func TestWriter(t *testing.T) {
for _, test := range tests {
b := new(bytes.Buffer)
w := NewWriter(b, test.wid, test.flag)
if _, err := w.Write([]byte(test.src)); err != nil {
t.Error(err)
}
if err := w.Flush(); err != nil {
t.Error(err)
}
if g := b.String(); test.want != g {
t.Log("\n" + test.want)
t.Log("\n" + g)
t.Errorf("%q != %q", test.want, g)
}
}
}

@ -0,0 +1,3 @@
// Package text provides rudimentary functions for manipulating text in
// paragraphs.
package text

@ -0,0 +1,74 @@
package text
import (
"io"
)
// Indent inserts prefix at the beginning of each non-empty line of s. The
// end-of-line marker is NL.
func Indent(s, prefix string) string {
return string(IndentBytes([]byte(s), []byte(prefix)))
}
// IndentBytes inserts prefix at the beginning of each non-empty line of b.
// The end-of-line marker is NL.
func IndentBytes(b, prefix []byte) []byte {
var res []byte
bol := true
for _, c := range b {
if bol && c != '\n' {
res = append(res, prefix...)
}
res = append(res, c)
bol = c == '\n'
}
return res
}
// Writer indents each line of its input.
type indentWriter struct {
w io.Writer
bol bool
pre [][]byte
sel int
off int
}
// NewIndentWriter makes a new write filter that indents the input
// lines. Each line is prefixed in order with the corresponding
// element of pre. If there are more lines than elements, the last
// element of pre is repeated for each subsequent line.
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
return &indentWriter{
w: w,
pre: pre,
bol: true,
}
}
// The only errors returned are from the underlying indentWriter.
func (w *indentWriter) Write(p []byte) (n int, err error) {
for _, c := range p {
if w.bol {
var i int
i, err = w.w.Write(w.pre[w.sel][w.off:])
w.off += i
if err != nil {
return n, err
}
}
_, err = w.w.Write([]byte{c})
if err != nil {
return n, err
}
n++
w.bol = c == '\n'
if w.bol {
w.off = 0
if w.sel < len(w.pre)-1 {
w.sel++
}
}
}
return n, nil
}

@ -0,0 +1,119 @@
package text
import (
"bytes"
"testing"
)
type T struct {
inp, exp, pre string
}
var tests = []T{
{
"The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n",
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n",
"xxx",
},
{
"The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.",
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.",
"xxx",
},
}
func TestIndent(t *testing.T) {
for _, test := range tests {
got := Indent(test.inp, test.pre)
if got != test.exp {
t.Errorf("mismatch %q != %q", got, test.exp)
}
}
}
type IndentWriterTest struct {
inp, exp string
pre []string
}
var ts = []IndentWriterTest{
{
`
The quick brown fox
jumps over the lazy
dog.
But not quickly.
`[1:],
`
xxxThe quick brown fox
xxxjumps over the lazy
xxxdog.
xxxBut not quickly.
`[1:],
[]string{"xxx"},
},
{
`
The quick brown fox
jumps over the lazy
dog.
But not quickly.
`[1:],
`
xxaThe quick brown fox
xxxjumps over the lazy
xxxdog.
xxxBut not quickly.
`[1:],
[]string{"xxa", "xxx"},
},
{
`
The quick brown fox
jumps over the lazy
dog.
But not quickly.
`[1:],
`
xxaThe quick brown fox
xxbjumps over the lazy
xxcdog.
xxxBut not quickly.
`[1:],
[]string{"xxa", "xxb", "xxc", "xxx"},
},
{
`
The quick brown fox
jumps over the lazy
dog.
But not quickly.`[1:],
`
xxaThe quick brown fox
xxxjumps over the lazy
xxxdog.
xxx
xxxBut not quickly.`[1:],
[]string{"xxa", "xxx"},
},
}
func TestIndentWriter(t *testing.T) {
for _, test := range ts {
b := new(bytes.Buffer)
pre := make([][]byte, len(test.pre))
for i := range test.pre {
pre[i] = []byte(test.pre[i])
}
w := NewIndentWriter(b, pre...)
if _, err := w.Write([]byte(test.inp)); err != nil {
t.Error(err)
}
if got := b.String(); got != test.exp {
t.Errorf("mismatch %q != %q", got, test.exp)
t.Log(got)
t.Log(test.exp)
}
}
}

@ -0,0 +1,9 @@
Command mc prints in multiple columns.
Usage: mc [-] [-N] [file...]
Mc splits the input into as many columns as will fit in N
print positions. If the output is a tty, the default N is
the number of characters in a terminal line; otherwise the
default N is 80. Under option - each input line ending in
a colon ':' is printed separately.

@ -0,0 +1,62 @@
// Command mc prints in multiple columns.
//
// Usage: mc [-] [-N] [file...]
//
// Mc splits the input into as many columns as will fit in N
// print positions. If the output is a tty, the default N is
// the number of characters in a terminal line; otherwise the
// default N is 80. Under option - each input line ending in
// a colon ':' is printed separately.
package main
import (
"github.com/kr/pty"
"github.com/kr/text/colwriter"
"io"
"log"
"os"
"strconv"
)
func main() {
var width int
var flag uint
args := os.Args[1:]
for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' {
if len(args[0]) > 1 {
width, _ = strconv.Atoi(args[0][1:])
} else {
flag |= colwriter.BreakOnColon
}
args = args[1:]
}
if width < 1 {
_, width, _ = pty.Getsize(os.Stdout)
}
if width < 1 {
width = 80
}
w := colwriter.NewWriter(os.Stdout, width, flag)
if len(args) > 0 {
for _, s := range args {
if f, err := os.Open(s); err == nil {
copyin(w, f)
f.Close()
} else {
log.Println(err)
}
}
} else {
copyin(w, os.Stdin)
}
}
func copyin(w *colwriter.Writer, r io.Reader) {
if _, err := io.Copy(w, r); err != nil {
log.Println(err)
}
if err := w.Flush(); err != nil {
log.Println(err)
}
}

@ -0,0 +1,86 @@
package text
import (
"bytes"
"math"
)
var (
nl = []byte{'\n'}
sp = []byte{' '}
)
const defaultPenalty = 1e5
// Wrap wraps s into a paragraph of lines of length lim, with minimal
// raggedness.
func Wrap(s string, lim int) string {
return string(WrapBytes([]byte(s), lim))
}
// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
// raggedness.
func WrapBytes(b []byte, lim int) []byte {
words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
var lines [][]byte
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
lines = append(lines, bytes.Join(line, sp))
}
return bytes.Join(lines, nl)
}
// WrapWords is the low-level line-breaking algorithm, useful if you need more
// control over the details of the text wrapping process. For most uses, either
// Wrap or WrapBytes will be sufficient and more convenient.
//
// WrapWords splits a list of words into lines with minimal "raggedness",
// treating each byte as one unit, accounting for spc units between adjacent
// words on each line, and attempting to limit lines to lim units. Raggedness
// is the total error over all lines, where error is the square of the
// difference of the length of the line and lim. Too-long lines (which only
// happen when a single word is longer than lim units) have pen penalty units
// added to the error.
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
n := len(words)
length := make([][]int, n)
for i := 0; i < n; i++ {
length[i] = make([]int, n)
length[i][i] = len(words[i])
for j := i + 1; j < n; j++ {
length[i][j] = length[i][j-1] + spc + len(words[j])
}
}
nbrk := make([]int, n)
cost := make([]int, n)
for i := range cost {
cost[i] = math.MaxInt32
}
for i := n - 1; i >= 0; i-- {
if length[i][n-1] <= lim || i == n-1 {
cost[i] = 0
nbrk[i] = n
} else {
for j := i + 1; j < n; j++ {
d := lim - length[i][j-1]
c := d*d + cost[j]
if length[i][j-1] > lim {
c += pen // too-long lines get a worse penalty
}
if c < cost[i] {
cost[i] = c
nbrk[i] = j
}
}
}
}
var lines [][][]byte
i := 0
for i < n {
lines = append(lines, words[i:nbrk[i]])
i = nbrk[i]
}
return lines
}

@ -0,0 +1,62 @@
package text
import (
"bytes"
"testing"
)
var text = "The quick brown fox jumps over the lazy dog."
func TestWrap(t *testing.T) {
exp := [][]string{
{"The", "quick", "brown", "fox"},
{"jumps", "over", "the", "lazy", "dog."},
}
words := bytes.Split([]byte(text), sp)
got := WrapWords(words, 1, 24, defaultPenalty)
if len(exp) != len(got) {
t.Fail()
}
for i := range exp {
if len(exp[i]) != len(got[i]) {
t.Fail()
}
for j := range exp[i] {
if exp[i][j] != string(got[i][j]) {
t.Fatal(i, exp[i][j], got[i][j])
}
}
}
}
func TestWrapNarrow(t *testing.T) {
exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog."
if Wrap(text, 5) != exp {
t.Fail()
}
}
func TestWrapOneLine(t *testing.T) {
exp := "The quick brown fox jumps over the lazy dog."
if Wrap(text, 500) != exp {
t.Fail()
}
}
func TestWrapBug1(t *testing.T) {
cases := []struct {
limit int
text string
want string
}{
{4, "aaaaa", "aaaaa"},
{4, "a aaaaa", "a\naaaaa"},
}
for _, test := range cases {
got := Wrap(test.text, test.limit)
if got != test.want {
t.Errorf("Wrap(%q, %d) = %q want %q", test.text, test.limit, got, test.want)
}
}
}

@ -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.5 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
git fetch https://github.com/grafana/grafana.git docs-2.6 && 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.6.0
3.0.0

@ -8,16 +8,16 @@ page_keywords: grafana, plugins, documentation
## Installing plugins
The easiest way to install plugins is by using the CLI tool grafana-cli which is bundled with grafana. Before any modification take place after modifieng plugins, grafana-server need to be restarted.
The easiest way to install plugins is by using the CLI tool grafana-cli which is bundled with grafana. Before any modification take place after modifying plugins, grafana-server needs to be restarted.
### Grafana plugin directory
On Linux systems the grafana-cli will assume that the grafana plugin directory is "/var/lib/grafana/plugins". Its possible to override the directory which grafana-cli will operate on by specifing the --path flag. On Windows systems this parameter have to be specified for every call.
On Linux systems the grafana-cli will assume that the grafana plugin directory is "/var/lib/grafana/plugins". It's possible to override the directory which grafana-cli will operate on by specifing the --path flag. On Windows systems this parameter have to be specified for every call.
### Grafana-cli commands
List available plugins
```
grafana-cli list-remove
grafana-cli list-remote
```
Install a plugin type

@ -175,9 +175,8 @@ func Register(r *macaron.Macaron) {
r.Group("/plugins", func() {
r.Get("/", wrap(GetPluginList))
r.Get("/dashboards/:pluginId", wrap(GetPluginDashboards))
r.Post("/dashboards/install", bind(dtos.InstallPluginDashboardCmd{}), wrap(InstallPluginDashboard))
r.Get("/:pluginId/readme", wrap(GetPluginReadme))
r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
}, reqOrgAdmin)
@ -193,6 +192,7 @@ func Register(r *macaron.Macaron) {
r.Get("/file/:file", GetDashboardFromJsonFile)
r.Get("/home", GetHomeDashboard)
r.Get("/tags", GetDashboardTags)
r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
})
// Dashboard snapshots

@ -130,11 +130,14 @@ func handleGetNamespaces(req *cwRequest, c *middleware.Context) {
for key := range metricsMap {
keys = append(keys, key)
}
if customMetricsNamespaces, ok := req.DataSource.JsonData["customMetricsNamespaces"].(string); ok {
for _, key := range strings.Split(customMetricsNamespaces, ",") {
customNamespaces := req.DataSource.JsonData.Get("customMetricsNamespaces").MustString()
if customNamespaces != "" {
for _, key := range strings.Split(customNamespaces, ",") {
keys = append(keys, key)
}
}
sort.Sort(sort.StringSlice(keys))
result := []interface{}{}

@ -53,7 +53,6 @@ func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapsho
}
func GetDashboardSnapshot(c *middleware.Context) {
key := c.Params(":key")
query := &m.GetDashboardSnapshotQuery{Key: key}
@ -136,5 +135,4 @@ func SearchDashboardSnapshots(c *middleware.Context) Response {
}
return Json(200, dtos)
//return Json(200, searchQuery.Result)
}

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
@ -52,26 +53,26 @@ type DashboardMeta struct {
}
type DashboardFullWithMeta struct {
Meta DashboardMeta `json:"meta"`
Dashboard map[string]interface{} `json:"dashboard"`
Meta DashboardMeta `json:"meta"`
Dashboard *simplejson.Json `json:"dashboard"`
}
type DataSource struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData,omitempty"`
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
}
type MetricQueryResultDto struct {

@ -26,9 +26,23 @@ type PluginListItem struct {
Info *plugins.PluginInfo `json:"info"`
}
type InstallPluginDashboardCmd struct {
PluginId string `json:"pluginId"`
Path string `json:"path"`
Reinstall bool `json:"reinstall"`
Inputs map[string]interface{} `json:"inputs"`
type PluginList []PluginListItem
func (slice PluginList) Len() int {
return len(slice)
}
func (slice PluginList) Less(i, j int) bool {
return slice[i].Name < slice[j].Name
}
func (slice PluginList) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
type ImportDashboardCommand struct {
PluginId string `json:"pluginId"`
Path string `json:"path"`
Reinstall bool `json:"reinstall"`
Inputs []plugins.ImportDashboardInput `json:"inputs"`
}

@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
defaultDatasource = ds.Name
}
if len(ds.JsonData) > 0 {
if len(ds.JsonData.MustMap()) > 0 {
dsMap["jsonData"] = ds.JsonData
}

@ -1,6 +1,8 @@
package api
import (
"sort"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
@ -19,7 +21,7 @@ func GetPluginList(c *middleware.Context) Response {
return ApiError(500, "Failed to get list of plugins", err)
}
result := make([]*dtos.PluginListItem, 0)
result := make(dtos.PluginList, 0)
for _, pluginDef := range plugins.Plugins {
// filter out app sub plugins
if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
@ -31,7 +33,7 @@ func GetPluginList(c *middleware.Context) Response {
continue
}
listItem := &dtos.PluginListItem{
listItem := dtos.PluginListItem{
Id: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
@ -48,9 +50,17 @@ func GetPluginList(c *middleware.Context) Response {
continue
}
// filter out built in data sources
if ds, exists := plugins.DataSources[pluginDef.Id]; exists {
if ds.BuiltIn {
continue
}
}
result = append(result, listItem)
}
sort.Sort(result)
return Json(200, result)
}
@ -122,9 +132,23 @@ func GetPluginDashboards(c *middleware.Context) Response {
}
}
func InstallPluginDashboard(c *middleware.Context, apiCmd dtos.InstallPluginDashboardCmd) Response {
func GetPluginReadme(c *middleware.Context) Response {
pluginId := c.Params(":pluginId")
if content, err := plugins.GetPluginReadme(pluginId); err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil)
}
return ApiError(500, "Could not get readme", err)
} else {
return Respond(200, content)
}
}
func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
cmd := plugins.InstallPluginDashboardCommand{
cmd := plugins.ImportDashboardCommand{
OrgId: c.OrgId,
UserId: c.UserId,
PluginId: apiCmd.PluginId,

@ -15,6 +15,7 @@ import (
"os"
"path"
"regexp"
"strings"
)
func validateInput(c CommandLine, pluginFolder string) error {
@ -156,7 +157,11 @@ func downloadFile(pluginName, filePath, url string) (err error) {
} else {
dst, err := os.Create(newFile)
if err != nil {
log.Errorf("%v", err)
if strings.Contains(err.Error(), "permission denied") {
return fmt.Errorf(
"Could not create file %s. permission deined. Make sure you have write access to plugindir",
newFile)
}
}
defer dst.Close()
src, err := zf.Open()

@ -0,0 +1,817 @@
// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
// MIT Licence
package dynmap
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
)
// Error values returned when validation functions fail
var (
ErrNotNull = errors.New("is not null")
ErrNotArray = errors.New("Not an array")
ErrNotNumber = errors.New("not a number")
ErrNotBool = errors.New("no bool")
ErrNotObject = errors.New("not an object")
ErrNotObjectArray = errors.New("not an object array")
ErrNotString = errors.New("not a string")
)
type KeyNotFoundError struct {
Key string
}
func (k KeyNotFoundError) Error() string {
if k.Key != "" {
return fmt.Sprintf("key '%s' not found", k.Key)
}
return "key not found"
}
// Value represents an arbitrary JSON value.
// It may contain a bool, number, string, object, array or null.
type Value struct {
data interface{}
exists bool // Used to separate nil and non-existing values
}
// Object represents an object JSON object.
// It inherets from Value but with an additional method to access
// a map representation of it's content. It's useful when iterating.
type Object struct {
Value
m map[string]*Value
valid bool
}
// Returns the golang map.
// Needed when iterating through the values of the object.
func (v *Object) Map() map[string]*Value {
return v.m
}
func NewFromMap(data map[string]interface{}) *Object {
val := &Value{data: data, exists: true}
obj, _ := val.Object()
return obj
}
func NewObject() *Object {
val := &Value{data: make(map[string]interface{}), exists: true}
obj, _ := val.Object()
return obj
}
// Creates a new value from an io.reader.
// Returns an error if the reader does not contain valid json.
// Useful for parsing the body of a net/http response.
// Example: NewFromReader(res.Body)
func NewValueFromReader(reader io.Reader) (*Value, error) {
j := new(Value)
d := json.NewDecoder(reader)
d.UseNumber()
err := d.Decode(&j.data)
return j, err
}
// Creates a new value from bytes.
// Returns an error if the bytes are not valid json.
func NewValueFromBytes(b []byte) (*Value, error) {
r := bytes.NewReader(b)
return NewValueFromReader(r)
}
func objectFromValue(v *Value, err error) (*Object, error) {
if err != nil {
return nil, err
}
o, err := v.Object()
if err != nil {
return nil, err
}
return o, nil
}
func NewObjectFromBytes(b []byte) (*Object, error) {
return objectFromValue(NewValueFromBytes(b))
}
func NewObjectFromReader(reader io.Reader) (*Object, error) {
return objectFromValue(NewValueFromReader(reader))
}
// Marshal into bytes.
func (v *Value) Marshal() ([]byte, error) {
return json.Marshal(v.data)
}
// Get the interyling data as interface
func (v *Value) Interface() interface{} {
return v.data
}
func (v *Value) StringMap() map[string]interface{} {
return v.data.(map[string]interface{})
}
// Private Get
func (v *Value) get(key string) (*Value, error) {
// Assume this is an object
obj, err := v.Object()
if err == nil {
child, ok := obj.Map()[key]
if ok {
return child, nil
} else {
return nil, KeyNotFoundError{key}
}
}
return nil, err
}
// Private get path
func (v *Value) getPath(keys []string) (*Value, error) {
current := v
var err error
for _, key := range keys {
current, err = current.get(key)
if err != nil {
return nil, err
}
}
return current, nil
}
// Gets the value at key path.
// Returns error if the value does not exist.
// Consider using the more specific Get<Type>(..) methods instead.
// Example:
// value, err := GetValue("address", "street")
func (v *Object) GetValue(keys ...string) (*Value, error) {
return v.getPath(keys)
}
// Gets the value at key path and attempts to typecast the value into an object.
// Returns error if the value is not a json object.
// Example:
// object, err := GetObject("person", "address")
func (v *Object) GetObject(keys ...string) (*Object, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
obj, err := child.Object()
if err != nil {
return nil, err
} else {
return obj, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into a string.
// Returns error if the value is not a json string.
// Example:
// string, err := GetString("address", "street")
func (v *Object) GetString(keys ...string) (string, error) {
child, err := v.getPath(keys)
if err != nil {
return "", err
} else {
return child.String()
}
}
func (v *Object) MustGetString(path string, def string) string {
keys := strings.Split(path, ".")
if str, err := v.GetString(keys...); err != nil {
return def
} else {
return str
}
}
// Gets the value at key path and attempts to typecast the value into null.
// Returns error if the value is not json null.
// Example:
// err := GetNull("address", "street")
func (v *Object) GetNull(keys ...string) error {
child, err := v.getPath(keys)
if err != nil {
return err
}
return child.Null()
}
// Gets the value at key path and attempts to typecast the value into a number.
// Returns error if the value is not a json number.
// Example:
// n, err := GetNumber("address", "street_number")
func (v *Object) GetNumber(keys ...string) (json.Number, error) {
child, err := v.getPath(keys)
if err != nil {
return "", err
} else {
n, err := child.Number()
if err != nil {
return "", err
} else {
return n, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
// n, err := GetNumber("address", "street_number")
func (v *Object) GetFloat64(keys ...string) (float64, error) {
child, err := v.getPath(keys)
if err != nil {
return 0, err
} else {
n, err := child.Float64()
if err != nil {
return 0, err
} else {
return n, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
// n, err := GetNumber("address", "street_number")
func (v *Object) GetInt64(keys ...string) (int64, error) {
child, err := v.getPath(keys)
if err != nil {
return 0, err
} else {
n, err := child.Int64()
if err != nil {
return 0, err
} else {
return n, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
// v, err := GetInterface("address", "anything")
func (v *Object) GetInterface(keys ...string) (interface{}, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
return child.Interface(), nil
}
}
// Gets the value at key path and attempts to typecast the value into a bool.
// Returns error if the value is not a json boolean.
// Example:
// married, err := GetBoolean("person", "married")
func (v *Object) GetBoolean(keys ...string) (bool, error) {
child, err := v.getPath(keys)
if err != nil {
return false, err
}
return child.Boolean()
}
// Gets the value at key path and attempts to typecast the value into an array.
// Returns error if the value is not a json array.
// Consider using the more specific Get<Type>Array() since it may reduce later type casts.
// Example:
// friends, err := GetValueArray("person", "friends")
// for i, friend := range friends {
// ... // friend will be of type Value here
// }
func (v *Object) GetValueArray(keys ...string) ([]*Value, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
return child.Array()
}
}
// Gets the value at key path and attempts to typecast the value into an array of objects.
// Returns error if the value is not a json array or if any of the contained objects are not objects.
// Example:
// friends, err := GetObjectArray("person", "friends")
// for i, friend := range friends {
// ... // friend will be of type Object here
// }
func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]*Object, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.
Object()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of string.
// Returns error if the value is not a json array or if any of the contained objects are not strings.
// Gets the value at key path and attempts to typecast the value into an array of objects.
// Returns error if the value is not a json array or if any of the contained objects are not objects.
// Example:
// friendNames, err := GetStringArray("person", "friend_names")
// for i, friendName := range friendNames {
// ... // friendName will be of type string here
// }
func (v *Object) GetStringArray(keys ...string) ([]string, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]string, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.String()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of numbers.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
// Example:
// friendAges, err := GetNumberArray("person", "friend_ages")
// for i, friendAge := range friendAges {
// ... // friendAge will be of type float64 here
// }
func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]json.Number, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.Number()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of floats.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]float64, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.Float64()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of ints.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
func (v *Object) GetInt64Array(keys ...string) ([]int64, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]int64, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.Int64()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of bools.
// Returns error if the value is not a json array or if any of the contained objects are not booleans.
func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) {
child, err := v.getPath(keys)
if err != nil {
return nil, err
} else {
array, err := child.Array()
if err != nil {
return nil, err
} else {
typedArray := make([]bool, len(array))
for index, arrayItem := range array {
typedArrayItem, err := arrayItem.Boolean()
if err != nil {
return nil, err
} else {
typedArray[index] = typedArrayItem
}
}
return typedArray, nil
}
}
}
// Gets the value at key path and attempts to typecast the value into an array of nulls.
// Returns length, or an error if the value is not a json array or if any of the contained objects are not nulls.
func (v *Object) GetNullArray(keys ...string) (int64, error) {
child, err := v.getPath(keys)
if err != nil {
return 0, err
} else {
array, err := child.Array()
if err != nil {
return 0, err
} else {
var length int64 = 0
for _, arrayItem := range array {
err := arrayItem.Null()
if err != nil {
return 0, err
} else {
length++
}
}
return length, nil
}
}
}
// Returns an error if the value is not actually null
func (v *Value) Null() error {
var valid bool
// Check the type of this data
switch v.data.(type) {
case nil:
valid = v.exists // Valid only if j also exists, since other values could possibly also be nil
break
}
if valid {
return nil
}
return ErrNotNull
}
// Attempts to typecast the current value into an array.
// Returns error if the current value is not a json array.
// Example:
// friendsArray, err := friendsValue.Array()
func (v *Value) Array() ([]*Value, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case []interface{}:
valid = true
break
}
// Unsure if this is a good way to use slices, it's probably not
var slice []*Value
if valid {
for _, element := range v.data.([]interface{}) {
child := Value{element, true}
slice = append(slice, &child)
}
return slice, nil
}
return slice, ErrNotArray
}
// Attempts to typecast the current value into a number.
// Returns error if the current value is not a json number.
// Example:
// ageNumber, err := ageValue.Number()
func (v *Value) Number() (json.Number, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case json.Number:
valid = true
break
}
if valid {
return v.data.(json.Number), nil
}
return "", ErrNotNumber
}
// Attempts to typecast the current value into a float64.
// Returns error if the current value is not a json number.
// Example:
// percentage, err := v.Float64()
func (v *Value) Float64() (float64, error) {
n, err := v.Number()
if err != nil {
return 0, err
}
return n.Float64()
}
// Attempts to typecast the current value into a int64.
// Returns error if the current value is not a json number.
// Example:
// id, err := v.Int64()
func (v *Value) Int64() (int64, error) {
n, err := v.Number()
if err != nil {
return 0, err
}
return n.Int64()
}
// Attempts to typecast the current value into a bool.
// Returns error if the current value is not a json boolean.
// Example:
// marriedBool, err := marriedValue.Boolean()
func (v *Value) Boolean() (bool, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case bool:
valid = true
break
}
if valid {
return v.data.(bool), nil
}
return false, ErrNotBool
}
// Attempts to typecast the current value into an object.
// Returns error if the current value is not a json object.
// Example:
// friendObject, err := friendValue.Object()
func (v *Value) Object() (*Object, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case map[string]interface{}:
valid = true
break
}
if valid {
obj := new(Object)
obj.valid = valid
m := make(map[string]*Value)
if valid {
for key, element := range v.data.(map[string]interface{}) {
m[key] = &Value{element, true}
}
}
obj.data = v.data
obj.m = m
return obj, nil
}
return nil, ErrNotObject
}
// Attempts to typecast the current value into an object arrau.
// Returns error if the current value is not an array of json objects
// Example:
// friendObjects, err := friendValues.ObjectArray()
func (v *Value) ObjectArray() ([]*Object, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case []interface{}:
valid = true
break
}
// Unsure if this is a good way to use slices, it's probably not
var slice []*Object
if valid {
for _, element := range v.data.([]interface{}) {
childValue := Value{element, true}
childObject, err := childValue.Object()
if err != nil {
return nil, ErrNotObjectArray
}
slice = append(slice, childObject)
}
return slice, nil
}
return nil, ErrNotObjectArray
}
// Attempts to typecast the current value into a string.
// Returns error if the current value is not a json string
// Example:
// nameObject, err := nameValue.String()
func (v *Value) String() (string, error) {
var valid bool
// Check the type of this data
switch v.data.(type) {
case string:
valid = true
break
}
if valid {
return v.data.(string), nil
}
return "", ErrNotString
}
// Returns the value a json formatted string.
// Note: The method named String() is used by golang's log method for logging.
// Example:
func (v *Object) String() string {
f, err := json.Marshal(v.data)
if err != nil {
return err.Error()
}
return string(f)
}
func (v *Object) SetValue(key string, value interface{}) *Value {
data := v.Interface().(map[string]interface{})
data[key] = value
return &Value{
data: value,
exists: true,
}
}

@ -0,0 +1,313 @@
// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
// MIT Licence
package dynmap
import (
"log"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type Assert struct {
T *testing.T
}
func NewAssert(t *testing.T) *Assert {
return &Assert{
T: t,
}
}
func (assert *Assert) True(value bool, message string) {
if value == false {
log.Panicln("Assert: ", message)
}
}
func TestFirst(t *testing.T) {
assert := NewAssert(t)
testJSON := `{
"name": "anton",
"age": 29,
"nothing": null,
"true": true,
"false": false,
"list": [
"first",
"second"
],
"list2": [
{
"street": "Street 42",
"city": "Stockholm"
},
{
"street": "Street 42",
"city": "Stockholm"
}
],
"address": {
"street": "Street 42",
"city": "Stockholm"
},
"country": {
"name": "Sweden"
}
}`
j, err := NewObjectFromBytes([]byte(testJSON))
a, err := j.GetObject("address")
assert.True(a != nil && err == nil, "failed to create json from string")
assert.True(err == nil, "failed to create json from string")
s, err := j.GetString("name")
assert.True(s == "anton" && err == nil, "name should be a string")
s = j.MustGetString("name", "fallback")
assert.True(s == "anton", "must get string")
s = j.MustGetString("adsasdas", "fallback")
assert.True(s == "fallback", "must get string return fallback")
s, err = j.GetString("name")
assert.True(s == "anton" && err == nil, "name shoud match")
s, err = j.GetString("address", "street")
assert.True(s == "Street 42" && err == nil, "street shoud match")
//log.Println("s: ", s.String())
_, err = j.GetNumber("age")
assert.True(err == nil, "age should be a number")
n, err := j.GetInt64("age")
assert.True(n == 29 && err == nil, "age mismatch")
ageInterface, err := j.GetInterface("age")
assert.True(ageInterface != nil, "should be defined")
assert.True(err == nil, "age interface error")
invalidInterface, err := j.GetInterface("not_existing")
assert.True(invalidInterface == nil, "should not give error here")
assert.True(err != nil, "should give error here")
age, err := j.GetValue("age")
assert.True(age != nil && err == nil, "age should exist")
age2, err := j.GetValue("age2")
assert.True(age2 == nil && err != nil, "age2 should not exist")
address, err := j.GetObject("address")
assert.True(address != nil && err == nil, "address should be an object")
//log.Println("address: ", address)
s, err = address.GetString("street")
addressAsString, err := j.GetString("address")
assert.True(addressAsString == "" && err != nil, "address should not be an string")
s, err = j.GetString("address", "street")
assert.True(s == "Street 42" && err == nil, "street mismatching")
s, err = j.GetString("address", "name2")
assert.True(s == "" && err != nil, "nonexistent string fail")
b, err := j.GetBoolean("true")
assert.True(b == true && err == nil, "bool true test")
b, err = j.GetBoolean("false")
assert.True(b == false && err == nil, "bool false test")
b, err = j.GetBoolean("invalid_field")
assert.True(b == false && err != nil, "bool invalid test")
list, err := j.GetValueArray("list")
assert.True(list != nil && err == nil, "list should be an array")
list2, err := j.GetValueArray("list2")
assert.True(list2 != nil && err == nil, "list2 should be an array")
list2Array, err := j.GetValueArray("list2")
assert.True(err == nil, "List2 should not return error on AsArray")
assert.True(len(list2Array) == 2, "List2 should should have length 2")
list2Value, err := j.GetValue("list2")
assert.True(err == nil, "List2 should not return error on value")
list2ObjectArray, err := list2Value.ObjectArray()
assert.True(err == nil, "list2Value should not return error on ObjectArray")
assert.True(len(list2ObjectArray) == 2, "list2ObjectArray should should have length 2")
for _, elementValue := range list2Array {
//assert.True(element.IsObject() == true, "first fail")
element, err := elementValue.Object()
s, err = element.GetString("street")
assert.True(s == "Street 42" && err == nil, "second fail")
}
obj, err := j.GetObject("country")
assert.True(obj != nil && err == nil, "country should not return error on AsObject")
for key, value := range obj.Map() {
assert.True(key == "name", "country name key incorrect")
s, err = value.String()
assert.True(s == "Sweden" && err == nil, "country name should be Sweden")
}
}
func TestSecond(t *testing.T) {
json := `
{
"data": [
{
"id": "X999_Y999",
"from": {
"name": "Tom Brady", "id": "X12"
},
"message": "Looking forward to 2010!",
"actions": [
{
"name": "Comment",
"link": "http://www.facebook.com/X999/posts/Y999"
},
{
"name": "Like",
"link": "http://www.facebook.com/X999/posts/Y999"
}
],
"type": "status",
"created_time": "2010-08-02T21:27:44+0000",
"updated_time": "2010-08-02T21:27:44+0000"
},
{
"id": "X998_Y998",
"from": {
"name": "Peyton Manning", "id": "X18"
},
"message": "Where's my contract?",
"actions": [
{
"name": "Comment",
"link": "http://www.facebook.com/X998/posts/Y998"
},
{
"name": "Like",
"link": "http://www.facebook.com/X998/posts/Y998"
}
],
"type": "status",
"created_time": "2010-08-02T21:27:44+0000",
"updated_time": "2010-08-02T21:27:44+0000"
}
]
}`
assert := NewAssert(t)
j, err := NewObjectFromBytes([]byte(json))
assert.True(j != nil && err == nil, "failed to parse json")
dataObject, err := j.GetObject("data")
assert.True(dataObject == nil && err != nil, "data should not be an object")
dataArray, err := j.GetObjectArray("data")
assert.True(dataArray != nil && err == nil, "data should be an object array")
for index, dataItem := range dataArray {
if index == 0 {
id, err := dataItem.GetString("id")
assert.True(id == "X999_Y999" && err == nil, "item id mismatch")
fromName, err := dataItem.GetString("from", "name")
assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
actions, err := dataItem.GetObjectArray("actions")
for index, action := range actions {
if index == 1 {
name, err := action.GetString("name")
assert.True(name == "Like" && err == nil, "name mismatch")
link, err := action.GetString("link")
assert.True(link == "http://www.facebook.com/X999/posts/Y999" && err == nil, "Like mismatch")
}
}
} else if index == 1 {
id, err := dataItem.GetString("id")
assert.True(id == "X998_Y998" && err == nil, "item id mismatch")
}
}
}
func TestErrors(t *testing.T) {
json := `
{
"string": "hello",
"number": 1,
"array": [1,2,3]
}`
errstr := "expected an error getting %s, but got '%s'"
j, err := NewObjectFromBytes([]byte(json))
if err != nil {
t.Fatal("failed to parse json")
}
if _, err = j.GetObject("string"); err != ErrNotObject {
t.Errorf(errstr, "object", err)
}
if err = j.GetNull("string"); err != ErrNotNull {
t.Errorf(errstr, "null", err)
}
if _, err = j.GetStringArray("string"); err != ErrNotArray {
t.Errorf(errstr, "array", err)
}
if _, err = j.GetStringArray("array"); err != ErrNotString {
t.Errorf(errstr, "string array", err)
}
if _, err = j.GetNumber("array"); err != ErrNotNumber {
t.Errorf(errstr, "number", err)
}
if _, err = j.GetBoolean("array"); err != ErrNotBool {
t.Errorf(errstr, "boolean", err)
}
if _, err = j.GetString("number"); err != ErrNotString {
t.Errorf(errstr, "string", err)
}
_, err = j.GetString("not_found")
if e, ok := err.(KeyNotFoundError); !ok {
t.Errorf(errstr, "key not found error", e)
}
}
func TestWriting(t *testing.T) {
Convey("When writing", t, func() {
j, _ := NewObjectFromBytes([]byte(`{}`))
j.SetValue("prop", "value")
So(j.MustGetString("prop", ""), ShouldEqual, "value")
})
}

@ -0,0 +1,468 @@
package simplejson
import (
"bytes"
"encoding/json"
"errors"
"log"
)
// returns the current implementation version
func Version() string {
return "0.5.0"
}
type Json struct {
data interface{}
}
func (j *Json) FromDB(data []byte) error {
j.data = make(map[string]interface{})
dec := json.NewDecoder(bytes.NewBuffer(data))
dec.UseNumber()
return dec.Decode(&j.data)
}
func (j *Json) ToDB() ([]byte, error) {
if j == nil || j.data == nil {
return nil, nil
}
return j.Encode()
}
// NewJson returns a pointer to a new `Json` object
// after unmarshaling `body` bytes
func NewJson(body []byte) (*Json, error) {
j := new(Json)
err := j.UnmarshalJSON(body)
if err != nil {
return nil, err
}
return j, nil
}
// New returns a pointer to a new, empty `Json` object
func New() *Json {
return &Json{
data: make(map[string]interface{}),
}
}
// New returns a pointer to a new, empty `Json` object
func NewFromAny(data interface{}) *Json {
return &Json{data: data}
}
// Interface returns the underlying data
func (j *Json) Interface() interface{} {
return j.data
}
// Encode returns its marshaled data as `[]byte`
func (j *Json) Encode() ([]byte, error) {
return j.MarshalJSON()
}
// EncodePretty returns its marshaled data as `[]byte` with indentation
func (j *Json) EncodePretty() ([]byte, error) {
return json.MarshalIndent(&j.data, "", " ")
}
// Implements the json.Marshaler interface.
func (j *Json) MarshalJSON() ([]byte, error) {
return json.Marshal(&j.data)
}
// Set modifies `Json` map by `key` and `value`
// Useful for changing single key/value in a `Json` object easily.
func (j *Json) Set(key string, val interface{}) {
m, err := j.Map()
if err != nil {
return
}
m[key] = val
}
// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path,
// and then finally writing in the value
func (j *Json) SetPath(branch []string, val interface{}) {
if len(branch) == 0 {
j.data = val
return
}
// in order to insert our branch, we need map[string]interface{}
if _, ok := (j.data).(map[string]interface{}); !ok {
// have to replace with something suitable
j.data = make(map[string]interface{})
}
curr := j.data.(map[string]interface{})
for i := 0; i < len(branch)-1; i++ {
b := branch[i]
// key exists?
if _, ok := curr[b]; !ok {
n := make(map[string]interface{})
curr[b] = n
curr = n
continue
}
// make sure the value is the right sort of thing
if _, ok := curr[b].(map[string]interface{}); !ok {
// have to replace with something suitable
n := make(map[string]interface{})
curr[b] = n
}
curr = curr[b].(map[string]interface{})
}
// add remaining k/v
curr[branch[len(branch)-1]] = val
}
// Del modifies `Json` map by deleting `key` if it is present.
func (j *Json) Del(key string) {
m, err := j.Map()
if err != nil {
return
}
delete(m, key)
}
// Get returns a pointer to a new `Json` object
// for `key` in its `map` representation
//
// useful for chaining operations (to traverse a nested JSON):
// js.Get("top_level").Get("dict").Get("value").Int()
func (j *Json) Get(key string) *Json {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}
}
}
return &Json{nil}
}
// GetPath searches for the item as specified by the branch
// without the need to deep dive using Get()'s.
//
// js.GetPath("top_level", "dict")
func (j *Json) GetPath(branch ...string) *Json {
jin := j
for _, p := range branch {
jin = jin.Get(p)
}
return jin
}
// GetIndex returns a pointer to a new `Json` object
// for `index` in its `array` representation
//
// this is the analog to Get when accessing elements of
// a json array instead of a json object:
// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int()
func (j *Json) GetIndex(index int) *Json {
a, err := j.Array()
if err == nil {
if len(a) > index {
return &Json{a[index]}
}
}
return &Json{nil}
}
// CheckGet returns a pointer to a new `Json` object and
// a `bool` identifying success or failure
//
// useful for chained operations when success is important:
// if data, ok := js.Get("top_level").CheckGet("inner"); ok {
// log.Println(data)
// }
func (j *Json) CheckGet(key string) (*Json, bool) {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}, true
}
}
return nil, false
}
// Map type asserts to `map`
func (j *Json) Map() (map[string]interface{}, error) {
if m, ok := (j.data).(map[string]interface{}); ok {
return m, nil
}
return nil, errors.New("type assertion to map[string]interface{} failed")
}
// Array type asserts to an `array`
func (j *Json) Array() ([]interface{}, error) {
if a, ok := (j.data).([]interface{}); ok {
return a, nil
}
return nil, errors.New("type assertion to []interface{} failed")
}
// Bool type asserts to `bool`
func (j *Json) Bool() (bool, error) {
if s, ok := (j.data).(bool); ok {
return s, nil
}
return false, errors.New("type assertion to bool failed")
}
// String type asserts to `string`
func (j *Json) String() (string, error) {
if s, ok := (j.data).(string); ok {
return s, nil
}
return "", errors.New("type assertion to string failed")
}
// Bytes type asserts to `[]byte`
func (j *Json) Bytes() ([]byte, error) {
if s, ok := (j.data).(string); ok {
return []byte(s), nil
}
return nil, errors.New("type assertion to []byte failed")
}
// StringArray type asserts to an `array` of `string`
func (j *Json) StringArray() ([]string, error) {
arr, err := j.Array()
if err != nil {
return nil, err
}
retArr := make([]string, 0, len(arr))
for _, a := range arr {
if a == nil {
retArr = append(retArr, "")
continue
}
s, ok := a.(string)
if !ok {
return nil, err
}
retArr = append(retArr, s)
}
return retArr, nil
}
// MustArray guarantees the return of a `[]interface{}` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// for i, v := range js.Get("results").MustArray() {
// fmt.Println(i, v)
// }
func (j *Json) MustArray(args ...[]interface{}) []interface{} {
var def []interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustArray() received too many arguments %d", len(args))
}
a, err := j.Array()
if err == nil {
return a
}
return def
}
// MustMap guarantees the return of a `map[string]interface{}` (with optional default)
//
// useful when you want to interate over map values in a succinct manner:
// for k, v := range js.Get("dictionary").MustMap() {
// fmt.Println(k, v)
// }
func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
var def map[string]interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustMap() received too many arguments %d", len(args))
}
a, err := j.Map()
if err == nil {
return a
}
return def
}
// MustString guarantees the return of a `string` (with optional default)
//
// useful when you explicitly want a `string` in a single value return context:
// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default"))
func (j *Json) MustString(args ...string) string {
var def string
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustString() received too many arguments %d", len(args))
}
s, err := j.String()
if err == nil {
return s
}
return def
}
// MustStringArray guarantees the return of a `[]string` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// for i, s := range js.Get("results").MustStringArray() {
// fmt.Println(i, s)
// }
func (j *Json) MustStringArray(args ...[]string) []string {
var def []string
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustStringArray() received too many arguments %d", len(args))
}
a, err := j.StringArray()
if err == nil {
return a
}
return def
}
// MustInt guarantees the return of an `int` (with optional default)
//
// useful when you explicitly want an `int` in a single value return context:
// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150))
func (j *Json) MustInt(args ...int) int {
var def int
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustInt() received too many arguments %d", len(args))
}
i, err := j.Int()
if err == nil {
return i
}
return def
}
// MustFloat64 guarantees the return of a `float64` (with optional default)
//
// useful when you explicitly want a `float64` in a single value return context:
// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150))
func (j *Json) MustFloat64(args ...float64) float64 {
var def float64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustFloat64() received too many arguments %d", len(args))
}
f, err := j.Float64()
if err == nil {
return f
}
return def
}
// MustBool guarantees the return of a `bool` (with optional default)
//
// useful when you explicitly want a `bool` in a single value return context:
// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true))
func (j *Json) MustBool(args ...bool) bool {
var def bool
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustBool() received too many arguments %d", len(args))
}
b, err := j.Bool()
if err == nil {
return b
}
return def
}
// MustInt64 guarantees the return of an `int64` (with optional default)
//
// useful when you explicitly want an `int64` in a single value return context:
// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150))
func (j *Json) MustInt64(args ...int64) int64 {
var def int64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustInt64() received too many arguments %d", len(args))
}
i, err := j.Int64()
if err == nil {
return i
}
return def
}
// MustUInt64 guarantees the return of an `uint64` (with optional default)
//
// useful when you explicitly want an `uint64` in a single value return context:
// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150))
func (j *Json) MustUint64(args ...uint64) uint64 {
var def uint64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustUint64() received too many arguments %d", len(args))
}
i, err := j.Uint64()
if err == nil {
return i
}
return def
}

@ -0,0 +1,89 @@
// +build go1.1
package simplejson
import (
"bytes"
"encoding/json"
"errors"
"io"
"reflect"
"strconv"
)
// Implements the json.Unmarshaler interface.
func (j *Json) UnmarshalJSON(p []byte) error {
dec := json.NewDecoder(bytes.NewBuffer(p))
dec.UseNumber()
return dec.Decode(&j.data)
}
// NewFromReader returns a *Json by decoding from an io.Reader
func NewFromReader(r io.Reader) (*Json, error) {
j := new(Json)
dec := json.NewDecoder(r)
dec.UseNumber()
err := dec.Decode(&j.data)
return j, err
}
// Float64 coerces into a float64
func (j *Json) Float64() (float64, error) {
switch j.data.(type) {
case json.Number:
return j.data.(json.Number).Float64()
case float32, float64:
return reflect.ValueOf(j.data).Float(), nil
case int, int8, int16, int32, int64:
return float64(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return float64(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Int coerces into an int
func (j *Json) Int() (int, error) {
switch j.data.(type) {
case json.Number:
i, err := j.data.(json.Number).Int64()
return int(i), err
case float32, float64:
return int(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return int(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return int(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Int64 coerces into an int64
func (j *Json) Int64() (int64, error) {
switch j.data.(type) {
case json.Number:
return j.data.(json.Number).Int64()
case float32, float64:
return int64(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return reflect.ValueOf(j.data).Int(), nil
case uint, uint8, uint16, uint32, uint64:
return int64(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Uint64 coerces into an uint64
func (j *Json) Uint64() (uint64, error) {
switch j.data.(type) {
case json.Number:
return strconv.ParseUint(j.data.(json.Number).String(), 10, 64)
case float32, float64:
return uint64(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return uint64(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return reflect.ValueOf(j.data).Uint(), nil
}
return 0, errors.New("invalid value type")
}

@ -0,0 +1,248 @@
package simplejson
import (
"encoding/json"
"testing"
"github.com/bmizerany/assert"
)
func TestSimplejson(t *testing.T) {
var ok bool
var err error
js, err := NewJson([]byte(`{
"test": {
"string_array": ["asdf", "ghjk", "zxcv"],
"string_array_null": ["abc", null, "efg"],
"array": [1, "2", 3],
"arraywithsubs": [{"subkeyone": 1},
{"subkeytwo": 2, "subkeythree": 3}],
"int": 10,
"float": 5.150,
"string": "simplejson",
"bool": true,
"sub_obj": {"a": 1}
}
}`))
assert.NotEqual(t, nil, js)
assert.Equal(t, nil, err)
_, ok = js.CheckGet("test")
assert.Equal(t, true, ok)
_, ok = js.CheckGet("missing_key")
assert.Equal(t, false, ok)
aws := js.Get("test").Get("arraywithsubs")
assert.NotEqual(t, nil, aws)
var awsval int
awsval, _ = aws.GetIndex(0).Get("subkeyone").Int()
assert.Equal(t, 1, awsval)
awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int()
assert.Equal(t, 2, awsval)
awsval, _ = aws.GetIndex(1).Get("subkeythree").Int()
assert.Equal(t, 3, awsval)
i, _ := js.Get("test").Get("int").Int()
assert.Equal(t, 10, i)
f, _ := js.Get("test").Get("float").Float64()
assert.Equal(t, 5.150, f)
s, _ := js.Get("test").Get("string").String()
assert.Equal(t, "simplejson", s)
b, _ := js.Get("test").Get("bool").Bool()
assert.Equal(t, true, b)
mi := js.Get("test").Get("int").MustInt()
assert.Equal(t, 10, mi)
mi2 := js.Get("test").Get("missing_int").MustInt(5150)
assert.Equal(t, 5150, mi2)
ms := js.Get("test").Get("string").MustString()
assert.Equal(t, "simplejson", ms)
ms2 := js.Get("test").Get("missing_string").MustString("fyea")
assert.Equal(t, "fyea", ms2)
ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"})
assert.Equal(t, ma2, []interface{}{"1", 2, "3"})
msa := js.Get("test").Get("string_array").MustStringArray()
assert.Equal(t, msa[0], "asdf")
assert.Equal(t, msa[1], "ghjk")
assert.Equal(t, msa[2], "zxcv")
msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"})
assert.Equal(t, msa2[0], "asdf")
assert.Equal(t, msa2[1], "ghjk")
assert.Equal(t, msa2[2], "zxcv")
msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"})
assert.Equal(t, msa3, []string{"1", "2", "3"})
mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false})
assert.Equal(t, mm2, map[string]interface{}{"found": false})
strs, err := js.Get("test").Get("string_array").StringArray()
assert.Equal(t, err, nil)
assert.Equal(t, strs[0], "asdf")
assert.Equal(t, strs[1], "ghjk")
assert.Equal(t, strs[2], "zxcv")
strs2, err := js.Get("test").Get("string_array_null").StringArray()
assert.Equal(t, err, nil)
assert.Equal(t, strs2[0], "abc")
assert.Equal(t, strs2[1], "")
assert.Equal(t, strs2[2], "efg")
gp, _ := js.GetPath("test", "string").String()
assert.Equal(t, "simplejson", gp)
gp2, _ := js.GetPath("test", "int").Int()
assert.Equal(t, 10, gp2)
assert.Equal(t, js.Get("test").Get("bool").MustBool(), true)
js.Set("float2", 300.0)
assert.Equal(t, js.Get("float2").MustFloat64(), 300.0)
js.Set("test2", "setTest")
assert.Equal(t, "setTest", js.Get("test2").MustString())
js.Del("test2")
assert.NotEqual(t, "setTest", js.Get("test2").MustString())
js.Get("test").Get("sub_obj").Set("a", 2)
assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt())
js.GetPath("test", "sub_obj").Set("a", 3)
assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt())
}
func TestStdlibInterfaces(t *testing.T) {
val := new(struct {
Name string `json:"name"`
Params *Json `json:"params"`
})
val2 := new(struct {
Name string `json:"name"`
Params *Json `json:"params"`
})
raw := `{"name":"myobject","params":{"string":"simplejson"}}`
assert.Equal(t, nil, json.Unmarshal([]byte(raw), val))
assert.Equal(t, "myobject", val.Name)
assert.NotEqual(t, nil, val.Params.data)
s, _ := val.Params.Get("string").String()
assert.Equal(t, "simplejson", s)
p, err := json.Marshal(val)
assert.Equal(t, nil, err)
assert.Equal(t, nil, json.Unmarshal(p, val2))
assert.Equal(t, val, val2) // stable
}
func TestSet(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
js.Set("baz", "bing")
s, err := js.GetPath("baz").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bing", s)
}
func TestReplace(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`))
assert.Equal(t, nil, err)
s, err := js.GetPath("baz").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bing", s)
}
func TestSetPath(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"foo", "bar"}, "baz")
s, err := js.GetPath("foo", "bar").String()
assert.Equal(t, nil, err)
assert.Equal(t, "baz", s)
}
func TestSetPathNoPath(t *testing.T) {
js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`))
assert.Equal(t, nil, err)
f := js.GetPath("some_number").MustFloat64(99.0)
assert.Equal(t, f, 1.0)
js.SetPath([]string{}, map[string]interface{}{"foo": "bar"})
s, err := js.GetPath("foo").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bar", s)
f = js.GetPath("some_number").MustFloat64(99.0)
assert.Equal(t, f, 99.0)
}
func TestPathWillAugmentExisting(t *testing.T) {
js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"this", "d"}, "dd")
cases := []struct {
path []string
outcome string
}{
{
path: []string{"this", "a"},
outcome: "aa",
},
{
path: []string{"this", "b"},
outcome: "bb",
},
{
path: []string{"this", "c"},
outcome: "cc",
},
{
path: []string{"this", "d"},
outcome: "dd",
},
}
for _, tc := range cases {
s, err := js.GetPath(tc.path...).String()
assert.Equal(t, nil, err)
assert.Equal(t, tc.outcome, s)
}
}
func TestPathWillOverwriteExisting(t *testing.T) {
// notice how "a" is 0.1 - but then we'll try to set at path a, foo
js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"this", "a", "foo"}, "bar")
s, err := js.GetPath("this", "a", "foo").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bar", s)
}

@ -1,6 +1,10 @@
package models
import "time"
import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
// DashboardSnapshot model
type DashboardSnapshot struct {
@ -17,7 +21,7 @@ type DashboardSnapshot struct {
Created time.Time
Updated time.Time
Dashboard map[string]interface{}
Dashboard *simplejson.Json
}
// DashboardSnapshotDTO without dashboard map
@ -40,9 +44,9 @@ type DashboardSnapshotDTO struct {
// COMMANDS
type CreateDashboardSnapshotCommand struct {
Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
Name string `json:"name" binding:"Required"`
Expires int64 `json:"expires"`
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
Name string `json:"name" binding:"Required"`
Expires int64 `json:"expires"`
// these are passed when storing an external snapshot ref
External bool `json:"external"`

@ -6,6 +6,7 @@ import (
"time"
"github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/components/simplejson"
)
// Typed errors
@ -37,14 +38,14 @@ type Dashboard struct {
CreatedBy int64
Title string
Data map[string]interface{}
Data *simplejson.Json
}
// NewDashboard creates a new dashboard
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
dash.Data = make(map[string]interface{})
dash.Data["title"] = title
dash.Data = simplejson.New()
dash.Data.Set("title", title)
dash.Title = title
dash.Created = time.Now()
dash.Updated = time.Now()
@ -54,34 +55,24 @@ func NewDashboard(title string) *Dashboard {
// GetTags turns the tags in data json into go string array
func (dash *Dashboard) GetTags() []string {
jsonTags := dash.Data["tags"]
if jsonTags == nil || jsonTags == "" {
return []string{}
}
arr := jsonTags.([]interface{})
b := make([]string, len(arr))
for i := range arr {
b[i] = arr[i].(string)
}
return b
return dash.Data.Get("tags").MustStringArray()
}
func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
dash := &Dashboard{}
dash.Data = data
dash.Title = dash.Data["title"].(string)
dash.Title = dash.Data.Get("title").MustString()
dash.UpdateSlug()
if dash.Data["id"] != nil {
dash.Id = int64(dash.Data["id"].(float64))
if id, err := dash.Data.Get("id").Float64(); err == nil {
dash.Id = int64(id)
if dash.Data["version"] != nil {
dash.Version = int(dash.Data["version"].(float64))
if version, err := dash.Data.Get("version").Float64(); err == nil {
dash.Version = int(version)
dash.Updated = time.Now()
}
} else {
dash.Data["version"] = 0
dash.Data.Set("version", 0)
dash.Created = time.Now()
dash.Updated = time.Now()
}
@ -92,9 +83,11 @@ func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
// GetDashboardModel turns the command into the savable model
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash := NewDashboardFromJson(cmd.Dashboard)
if dash.Data["version"] == 0 {
if dash.Data.Get("version").MustInt(0) == 0 {
dash.CreatedBy = cmd.UserId
}
dash.UpdatedBy = cmd.UserId
dash.OrgId = cmd.OrgId
dash.UpdateSlug()
@ -103,15 +96,12 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
// GetString a
func (dash *Dashboard) GetString(prop string, defaultValue string) string {
if val, exists := dash.Data[prop]; exists {
return val.(string)
}
return defaultValue
return dash.Data.Get(prop).MustString(defaultValue)
}
// UpdateSlug updates the slug
func (dash *Dashboard) UpdateSlug() {
title := strings.ToLower(dash.Data["title"].(string))
title := strings.ToLower(dash.Data.Get("title").MustString())
dash.Slug = slug.Make(title)
}
@ -120,10 +110,10 @@ func (dash *Dashboard) UpdateSlug() {
//
type SaveDashboardCommand struct {
Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
Result *Dashboard
}

@ -3,6 +3,7 @@ package models
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
. "github.com/smartystreets/goconvey/convey"
)
@ -16,12 +17,11 @@ func TestDashboardModel(t *testing.T) {
})
Convey("Given a dashboard json", t, func() {
json := map[string]interface{}{
"title": "test dash",
}
json := simplejson.New()
json.Set("title", "test dash")
Convey("With tags as string value", func() {
json["tags"] = ""
json.Set("tags", "")
dash := NewDashboardFromJson(json)
So(len(dash.GetTags()), ShouldEqual, 0)

@ -3,6 +3,8 @@ package models
import (
"errors"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
const (
@ -42,7 +44,7 @@ type DataSource struct {
BasicAuthPassword string
WithCredentials bool
IsDefault bool
JsonData map[string]interface{}
JsonData *simplejson.Json
Created time.Time
Updated time.Time
@ -74,19 +76,19 @@ func IsKnownDataSourcePlugin(dsType string) bool {
// Also acts as api DTO
type AddDataSourceCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
Database string `json:"database"`
User string `json:"user"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
Database string `json:"database"`
User string `json:"user"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"`
@ -95,19 +97,19 @@ type AddDataSourceCommand struct {
// Also acts as api DTO
type UpdateDataSourceCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"`
Id int64 `json:"-"`

@ -0,0 +1,172 @@
package plugins
import (
"encoding/json"
"fmt"
"regexp"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
type ImportDashboardCommand struct {
Path string `json:"string"`
Inputs []ImportDashboardInput `json:"inputs"`
OrgId int64 `json:"-"`
UserId int64 `json:"-"`
PluginId string `json:"-"`
Result *PluginDashboardInfoDTO
}
type ImportDashboardInput struct {
Type string `json:"type"`
PluginId string `json:"pluginId"`
Name string `json:"name"`
Value string `json:"value"`
}
type DashboardInputMissingError struct {
VariableName string
}
func (e DashboardInputMissingError) Error() string {
return fmt.Sprintf("Dashbord input variable: %v missing from import command", e.VariableName)
}
func init() {
bus.AddHandler("plugins", ImportDashboard)
}
func ImportDashboard(cmd *ImportDashboardCommand) error {
plugin, exists := Plugins[cmd.PluginId]
if !exists {
return PluginNotFoundError{cmd.PluginId}
}
var dashboard *m.Dashboard
var err error
if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil {
return err
}
evaluator := &DashTemplateEvaluator{
template: dashboard.Data,
inputs: cmd.Inputs,
}
generatedDash, err := evaluator.Eval()
if err != nil {
return err
}
saveCmd := m.SaveDashboardCommand{
Dashboard: generatedDash,
OrgId: cmd.OrgId,
UserId: cmd.UserId,
}
if err := bus.Dispatch(&saveCmd); err != nil {
return err
}
cmd.Result = &PluginDashboardInfoDTO{
PluginId: cmd.PluginId,
Title: dashboard.Title,
Path: cmd.Path,
Revision: dashboard.GetString("revision", "1.0"),
InstalledUri: "db/" + saveCmd.Result.Slug,
InstalledRevision: dashboard.GetString("revision", "1.0"),
Installed: true,
}
return nil
}
type DashTemplateEvaluator struct {
template *simplejson.Json
inputs []ImportDashboardInput
variables map[string]string
result *simplejson.Json
varRegex *regexp.Regexp
}
func (this *DashTemplateEvaluator) findInput(varName string, varType string) *ImportDashboardInput {
for _, input := range this.inputs {
if varType == input.Type && (input.Name == varName || input.Name == "*") {
return &input
}
}
return nil
}
func (this *DashTemplateEvaluator) Eval() (*simplejson.Json, error) {
this.result = simplejson.New()
this.variables = make(map[string]string)
this.varRegex, _ = regexp.Compile(`(\$\{\w+\})`)
// check that we have all inputs we need
for _, inputDef := range this.template.Get("__inputs").MustArray() {
inputDefJson := simplejson.NewFromAny(inputDef)
inputName := inputDefJson.Get("name").MustString()
inputType := inputDefJson.Get("type").MustString()
input := this.findInput(inputName, inputType)
if input == nil {
return nil, &DashboardInputMissingError{VariableName: inputName}
}
this.variables["${"+inputName+"}"] = input.Value
}
return simplejson.NewFromAny(this.evalObject(this.template)), nil
}
func (this *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{} {
sourceValue := source.Interface()
switch v := sourceValue.(type) {
case string:
interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string {
if replacement, exists := this.variables[match]; exists {
return replacement
} else {
return match
}
})
return interpolated
case bool:
return v
case json.Number:
return v
case map[string]interface{}:
return this.evalObject(source)
case []interface{}:
array := make([]interface{}, 0)
for _, item := range v {
array = append(array, this.evalValue(simplejson.NewFromAny(item)))
}
return array
}
return nil
}
func (this *DashTemplateEvaluator) evalObject(source *simplejson.Json) interface{} {
result := make(map[string]interface{})
for key, value := range source.MustMap() {
if key == "__inputs" {
continue
}
result[key] = this.evalValue(simplejson.NewFromAny(value))
}
return result
}

@ -0,0 +1,94 @@
package plugins
import (
"io/ioutil"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ini.v1"
)
func TestDashboardImport(t *testing.T) {
Convey("When importing plugin dashboard", t, func() {
setting.Cfg = ini.Empty()
sec, _ := setting.Cfg.NewSection("plugin.test-app")
sec.NewKey("path", "../../tests/test-app")
err := Init()
So(err, ShouldBeNil)
var importedDash *m.Dashboard
bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
importedDash = cmd.GetDashboardModel()
cmd.Result = importedDash
return nil
})
cmd := ImportDashboardCommand{
PluginId: "test-app",
Path: "dashboards/connections.json",
OrgId: 1,
UserId: 1,
Inputs: []ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "graphite"},
},
}
err = ImportDashboard(&cmd)
So(err, ShouldBeNil)
Convey("should install dashboard", func() {
So(importedDash, ShouldNotBeNil)
resultStr, _ := importedDash.Data.EncodePretty()
expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json")
expectedJson, _ := simplejson.NewJson(expectedBytes)
expectedStr, _ := expectedJson.EncodePretty()
So(string(resultStr), ShouldEqual, string(expectedStr))
panel := importedDash.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0)
So(panel.Get("datasource").MustString(), ShouldEqual, "graphite")
})
})
Convey("When evaling dashboard template", t, func() {
template, _ := simplejson.NewJson([]byte(`{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource"
}
],
"test": {
"prop": "${DS_NAME}"
}
}`))
evaluator := &DashTemplateEvaluator{
template: template,
inputs: []ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "my-server"},
},
}
res, err := evaluator.Eval()
So(err, ShouldBeNil)
Convey("should render template", func() {
So(res.GetPath("test", "prop").MustString(), ShouldEqual, "my-server")
})
Convey("should not include inputs in output", func() {
inputs := res.Get("__inputs")
So(inputs.Interface(), ShouldBeNil)
})
})
}

@ -1,57 +0,0 @@
package plugins
import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
type InstallPluginDashboardCommand struct {
Path string `json:"string"`
Inputs map[string]interface{} `json:"inputs"`
OrgId int64 `json:"-"`
UserId int64 `json:"-"`
PluginId string `json:"-"`
Result *PluginDashboardInfoDTO
}
func init() {
bus.AddHandler("plugins", InstallPluginDashboard)
}
func InstallPluginDashboard(cmd *InstallPluginDashboardCommand) error {
plugin, exists := Plugins[cmd.PluginId]
if !exists {
return PluginNotFoundError{cmd.PluginId}
}
var dashboard *m.Dashboard
var err error
if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil {
return err
}
saveCmd := m.SaveDashboardCommand{
Dashboard: dashboard.Data,
OrgId: cmd.OrgId,
UserId: cmd.UserId,
}
if err := bus.Dispatch(&saveCmd); err != nil {
return err
}
cmd.Result = &PluginDashboardInfoDTO{
PluginId: cmd.PluginId,
Title: dashboard.Title,
Path: cmd.Path,
Revision: dashboard.GetString("revision", "1.0"),
InstalledUri: "db/" + saveCmd.Result.Slug,
InstalledRevision: dashboard.GetString("revision", "1.0"),
Installed: true,
}
return nil
}

@ -1,11 +1,11 @@
package plugins
import (
"encoding/json"
"os"
"path/filepath"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
@ -52,10 +52,8 @@ func loadPluginDashboard(plugin *PluginBase, path string) (*m.Dashboard, error)
defer reader.Close()
jsonParser := json.NewDecoder(reader)
var data map[string]interface{}
if err := jsonParser.Decode(&data); err != nil {
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}

@ -23,7 +23,7 @@ func TestPluginDashboards(t *testing.T) {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
if query.Slug == "nginx-connections" {
dash := m.NewDashboard("Nginx Connections")
dash.Data["revision"] = "1.1"
dash.Data.Set("revision", "1.1")
query.Result = dash
return nil
}

@ -3,9 +3,9 @@ package plugins
import (
"net/url"
"path"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -14,26 +14,33 @@ type FrontendPluginBase struct {
}
func (fp *FrontendPluginBase) initFrontendPlugin() {
if fp.StaticRoot != "" {
fp.StaticRootAbs = filepath.Join(fp.PluginDir, fp.StaticRoot)
if isInternalPlugin(fp.PluginDir) {
StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
Directory: fp.StaticRootAbs,
Directory: fp.PluginDir,
PluginId: fp.Id,
})
}
fp.handleModuleDefaults()
fp.Info.Logos.Small = evalRelativePluginUrlPath(fp.Info.Logos.Small, fp.BaseUrl)
fp.Info.Logos.Large = evalRelativePluginUrlPath(fp.Info.Logos.Large, fp.BaseUrl)
fp.Info.Logos.Small = getPluginLogoUrl(fp.Info.Logos.Small, fp.BaseUrl)
fp.Info.Logos.Large = getPluginLogoUrl(fp.Info.Logos.Large, fp.BaseUrl)
for i := 0; i < len(fp.Info.Screenshots); i++ {
fp.Info.Screenshots[i].Path = evalRelativePluginUrlPath(fp.Info.Screenshots[i].Path, fp.BaseUrl)
}
}
func getPluginLogoUrl(path, baseUrl string) string {
if path == "" {
return "public/img/plugin-default-logo_dark.svg"
}
return evalRelativePluginUrlPath(path, baseUrl)
}
func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
appSubPath := strings.Replace(fp.PluginDir, app.StaticRootAbs, "", 1)
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1)
fp.IncludedInAppId = app.Id
fp.BaseUrl = app.BaseUrl
fp.Module = util.JoinUrlFragments("plugins/"+app.Id, appSubPath) + "/module"
@ -41,7 +48,7 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
func (fp *FrontendPluginBase) handleModuleDefaults() {
if fp.StaticRoot != "" {
if isInternalPlugin(fp.PluginDir) {
fp.Module = path.Join("plugins", fp.Id, "module")
fp.BaseUrl = path.Join("public/plugins", fp.Id)
return
@ -51,6 +58,10 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, fp.Id)
}
func isInternalPlugin(pluginDir string) bool {
return !strings.Contains(pluginDir, setting.StaticRootPath)
}
func evalRelativePluginUrlPath(pathStr string, baseUrl string) string {
if pathStr == "" {
return ""

@ -30,19 +30,20 @@ type PluginLoader interface {
}
type PluginBase struct {
Type string `json:"type"`
Name string `json:"name"`
Id string `json:"id"`
Info PluginInfo `json:"info"`
Dependencies PluginDependencies `json:"dependencies"`
Includes []*PluginInclude `json:"includes"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
StaticRoot string `json:"staticRoot"`
StaticRootAbs string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
Id string `json:"id"`
Info PluginInfo `json:"info"`
Dependencies PluginDependencies `json:"dependencies"`
Includes []*PluginInclude `json:"includes"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
// cache for readme file contents
Readme []byte `json:"-"`
}
func (pb *PluginBase) registerPlugin(pluginDir string) error {

@ -3,6 +3,7 @@ package plugins
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path"
"path/filepath"
@ -155,3 +156,31 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
reader.Seek(0, 0)
return loader.Load(jsonParser, currentDir)
}
func GetPluginReadme(pluginId string) ([]byte, error) {
plug, exists := Plugins[pluginId]
if !exists {
return nil, PluginNotFoundError{pluginId}
}
if plug.Readme != nil {
return plug.Readme, nil
}
readmePath := filepath.Join(plug.PluginDir, "README.md")
if _, err := os.Stat(readmePath); os.IsNotExist(err) {
readmePath = filepath.Join(plug.PluginDir, "readme.md")
}
if _, err := os.Stat(readmePath); os.IsNotExist(err) {
plug.Readme = make([]byte, 0)
return plug.Readme, nil
}
if readmeBytes, err := ioutil.ReadFile(readmePath); err != nil {
return nil, err
} else {
plug.Readme = readmeBytes
return plug.Readme, nil
}
}

@ -1,12 +1,12 @@
package search
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
@ -120,10 +120,8 @@ func loadDashboardFromFile(filename string) (*JsonDashIndexItem, error) {
}
defer reader.Close()
jsonParser := json.NewDecoder(reader)
var data map[string]interface{}
if err := jsonParser.Decode(&data); err != nil {
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}

@ -69,7 +69,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
affectedRows, err = sess.Insert(dash)
} else {
dash.Version += 1
dash.Data["version"] = dash.Version
dash.Data.Set("version", dash.Version)
affectedRows, err = sess.Id(dash.Id).Update(dash)
}
@ -108,7 +108,7 @@ func GetDashboard(query *m.GetDashboardQuery) error {
return m.ErrDashboardNotFound
}
dashboard.Data["id"] = dashboard.Id
dashboard.Data.Set("id", dashboard.Id)
query.Result = &dashboard
return nil

@ -5,6 +5,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
@ -16,9 +17,9 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
Convey("Given saved snaphot", func() {
cmd := m.CreateDashboardSnapshotCommand{
Key: "hej",
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"hello": "mupp",
},
}),
}
err := CreateDashboardSnapshot(&cmd)
So(err, ShouldBeNil)
@ -29,7 +30,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(query.Result.Dashboard["hello"], ShouldEqual, "mupp")
So(query.Result.Dashboard.Get("hello").MustString(), ShouldEqual, "mupp")
})
})

@ -5,6 +5,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
)
@ -12,11 +13,11 @@ import (
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
cmd := m.SaveDashboardCommand{
OrgId: orgId,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
},
}),
}
err := SaveDashboard(&cmd)
@ -58,11 +59,11 @@ func TestDashboardDataAccess(t *testing.T) {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)
@ -76,11 +77,11 @@ func TestDashboardDataAccess(t *testing.T) {
cmd := m.SaveDashboardCommand{
OrgId: 2,
Overwrite: true,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(query.Result.Id),
"title": "Expect error",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)
@ -135,11 +136,11 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should not be able to save dashboard with same name", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "test dash 23",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)

@ -0,0 +1,74 @@
///<reference path="../../../headers/common.d.ts" />
import config from 'app/core/config';
import store from 'app/core/store';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from 'app/core/core_module';
var template = `
<div class="layout-selector">
<button ng-click="ctrl.listView()" ng-class="{active: ctrl.mode === 'list'}">
<i class="fa fa-list"></i>
</button>
<button ng-click="ctrl.gridView()" ng-class="{active: ctrl.mode === 'grid'}">
<i class="fa fa-th"></i>
</button>
</div>
`;
export class LayoutSelectorCtrl {
mode: string;
/** @ngInject **/
constructor(private $rootScope) {
this.mode = store.get('grafana.list.layout.mode') || 'grid';
}
listView() {
this.mode = 'list';
store.set('grafana.list.layout.mode', 'list');
this.$rootScope.appEvent('layout-mode-changed', 'list');
}
gridView() {
this.mode = 'grid';
store.set('grafana.list.layout.mode', 'grid');
this.$rootScope.appEvent('layout-mode-changed', 'grid');
}
}
/** @ngInject **/
export function layoutSelector() {
return {
restrict: 'E',
controller: LayoutSelectorCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: {},
template: template,
};
}
/** @ngInject **/
export function layoutMode($rootScope) {
return {
restrict: 'A',
scope: {},
link: function(scope, elem) {
var layout = store.get('grafana.list.layout.mode') || 'grid';
var className = 'card-list-layout-' + layout;
elem.addClass(className);
$rootScope.onAppEvent('layout-mode-changed', (evt, newLayout) => {
elem.removeClass(className);
className = 'card-list-layout-' + newLayout;
elem.addClass(className);
}, scope);
}
};
}
coreModule.directive('layoutSelector', layoutSelector);
coreModule.directive('layoutMode', layoutMode);

@ -28,6 +28,7 @@ import {infoPopover} from './components/info_popover';
import {colorPicker} from './components/colorpicker';
import {navbarDirective} from './components/navbar/navbar';
import {arrayJoin} from './directives/array_join';
import {layoutSelector} from './components/layout_selector/layout_selector';
import 'app/core/controllers/all';
import 'app/core/services/all';
import 'app/core/routes/routes';
@ -42,5 +43,6 @@ export {
navbarDirective,
searchDirective,
colorPicker,
layoutSelector,
infoPopover
};

@ -171,7 +171,7 @@ function($, _) {
kbn.describe_interval = function (string) {
var matches = string.match(kbn.interval_regex);
if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) {
throw new Error('Invalid interval string, expexcting a number followed by one of "Mwdhmsy"');
throw new Error('Invalid interval string, expecting a number followed by one of "Mwdhmsy"');
} else {
return {
sec: kbn.intervals_in_seconds[matches[2]],

@ -137,8 +137,8 @@ export class DashNavCtrl {
$scope.deleteDashboard = function() {
$scope.appEvent('confirm-modal', {
title: 'Delete dashboard',
text: 'Do you want to delete dashboard?',
title: 'Delete',
text: 'Do you want to delete this dashboard?',
text2: $scope.dashboard.title,
icon: 'fa-trash',
yesText: 'Delete',

@ -2,14 +2,16 @@
import store from 'app/core/store';
import _ from 'lodash';
import config from 'app/core/config';
export class ImpressionsStore {
constructor() {}
addDashboardImpression(dashboardId) {
var impressionsKey = this.impressionKey(config);
var impressions = [];
if (store.exists("dashboard_impressions")) {
impressions = JSON.parse(store.get("dashboard_impressions"));
if (store.exists(impressionsKey)) {
impressions = JSON.parse(store.get(impressionsKey));
if (!_.isArray(impressions)) {
impressions = [];
}
@ -24,11 +26,11 @@ export class ImpressionsStore {
if (impressions.length > 50) {
impressions.pop();
}
store.set("dashboard_impressions", JSON.stringify(impressions));
store.set(impressionsKey, JSON.stringify(impressions));
}
getDashboardOpened() {
var impressions = store.get("dashboard_impressions") || "[]";
var impressions = store.get(this.impressionKey(config)) || "[]";
impressions = JSON.parse(impressions);
@ -38,6 +40,10 @@ export class ImpressionsStore {
return impressions;
}
impressionKey(config) {
return "dashboard_impressions-" + config.bootData.user.orgId;
}
}
var impressions = new ImpressionsStore();

@ -24,6 +24,7 @@ export class DataSourceEditCtrl {
datasourceMeta: any;
tabIndex: number;
hasDashboards: boolean;
editForm: any;
/** @ngInject */
constructor(
@ -114,7 +115,7 @@ export class DataSourceEditCtrl {
}
saveChanges(test) {
if (!this.$scope.editForm.$valid) {
if (!this.editForm.$valid) {
return;
}

@ -17,17 +17,17 @@
v{{dash.revision}}
</td>
<td ng-if="dash.installed">
Installed v{{dash.installedRevision}}
Imported v{{dash.installedRevision}}
</td>
<td style="text-align: right">
<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
Install
Import
</button>
<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
Re-Install
Re-Import
</button>
<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
Un-install
Delete
</button>
</td>
</tr>

@ -7,11 +7,12 @@ import coreModule from 'app/core/core_module';
export class DashImportListCtrl {
dashboards: any[];
plugin: any;
datasource: any;
constructor(private $http, private backendSrv, private $rootScope) {
this.dashboards = [];
backendSrv.get(`/api/plugins/dashboards/${this.plugin.id}`).then(dashboards => {
backendSrv.get(`/api/plugins/${this.plugin.id}/dashboards`).then(dashboards => {
this.dashboards = dashboards;
});
}
@ -21,10 +22,19 @@ export class DashImportListCtrl {
pluginId: this.plugin.id,
path: dash.path,
reinstall: reinstall,
inputs: {}
inputs: []
};
this.backendSrv.post(`/api/plugins/dashboards/install`, installCmd).then(res => {
if (this.datasource) {
installCmd.inputs.push({
name: '*',
type: 'datasource',
pluginId: this.datasource.type,
value: this.datasource.name
});
}
this.backendSrv.post(`/api/dashboards/import`, installCmd).then(res => {
this.$rootScope.appEvent('alert-success', ['Dashboard Installed', dash.title]);
_.extend(dash, res);
});
@ -46,7 +56,8 @@ export function dashboardImportList() {
bindToController: true,
controllerAs: 'ctrl',
scope: {
plugin: "="
plugin: "=",
datasource: "="
}
};
}

@ -25,7 +25,7 @@
<div ng-if="ctrl.tabIndex === 0" class="tab-content">
<form name="editForm">
<form name="ctrl.editForm">
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-7">Name</span>
@ -52,12 +52,12 @@
</plugin-component>
</rebuild-on-change>
<div ng-if="testing" style="margin-top: 25px">
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<h5 ng-show="testing.done">Test results</h5>
<div class="alert-{{testing.status}} alert">
<div class="alert-title">{{testing.title}}</div>
<div ng-bind='testing.message'></div>
<div ng-if="ctrl.testing" style="margin-top: 25px">
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<h5 ng-show="ctrl.testing.done">Test results</h5>
<div class="alert-{{ctrl.testing.status}} alert">
<div class="alert-title">{{ctrl.testing.title}}</div>
<div ng-bind='ctrl.testing.message'></div>
</div>
</div>
@ -74,7 +74,7 @@
</div>
<div ng-if="ctrl.tabIndex === 1" class="tab-content">
<dashboard-import-list plugin="ctrl.datasourceMeta"></dashboard-import-list>
<dashboard-import-list plugin="ctrl.datasourceMeta" datasource="ctrl.current"></dashboard-import-list>
</div>
</div>

@ -1,7 +1,4 @@
<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
<a href="plugins/{{ctrl.model.pluginId}}/edit" class="navbar-page-btn">
{{ctrl.model.name}}
</a>
</navbar>
<div class="page-container" ng-init="ctrl.init()">
@ -23,7 +20,7 @@
</div>
<ul class="gf-tabs">
<li class="gf-tabs-item" ng-repeat="tab in ::['Overview', 'Config']">
<li class="gf-tabs-item" ng-repeat="tab in ctrl.tabs">
<a class="gf-tabs-link" ng-click="ctrl.tabIndex = $index" ng-class="{active: ctrl.tabIndex === $index}">
{{::tab}}
</a>
@ -39,21 +36,18 @@
</div>
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 1">
<div class="gf-form-inline">
<div class="gf-form">
<editor-checkbox text="Enabled" model="ctrl.model.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
</div>
<div class="gf-form">
<editor-checkbox text="Pinned" model="ctrl.model.pinned" change="ctrl.togglePinned()"></editor-checkbox>
</div>
</div>
<div ng-if="ctrl.model.id">
<plugin-component type="app-config-ctrl"></plugin-component>
<div class="clearfix"></div>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.enable()" ng-show="!ctrl.model.enabled">Enable</button>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()" ng-show="ctrl.model.enabled">Update</button>
</div>
</div>
</div>
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 2">
<dashboard-import-list plugin="ctrl.model"></dashboard-import-list>
</div>
<aside class="page-sidebar">

@ -4,39 +4,50 @@
<div class="page-container">
<div class="page-header">
<h1>Plugins</h1>
</div>
<table class="filter-table">
<thead>
<tr>
<th><strong>Name</strong></th>
<th><strong>Type</strong></th>
<th style="width: 60px;"></th>
<th style="width: 80px;"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="plugin in ctrl.plugins">
<td>
<a href="plugins/{{plugin.id}}/edit">
{{plugin.name}}
<div class="page-header-tabs">
<ul class="gf-tabs">
<li class="gf-tabs-item">
<a class="gf-tabs-link" href="plugins?type=panel" ng-class="{active: ctrl.tabIndex === 0}">
Panels
</a>
</li>
<li class="gf-tabs-item">
<a class="gf-tabs-link" href="plugins?type=datasource" ng-class="{active: ctrl.tabIndex === 1}">
Data sources
</a>
</li>
<li class="gf-tabs-item">
<a class="gf-tabs-link" href="plugins?type=app" ng-class="{active: ctrl.tabIndex === 2}">
Apps
</a>
</td>
<td>
{{plugin.type}}
</td>
<td>
<span class="label label-info" ng-if="plugin.enabled">Enabled</span>
<span class="label label-info" ng-if="plugin.pinned">Pinned</span>
</td>
<td class="text-right">
<a href="plugins/{{plugin.id}}/edit" class="btn btn-inverse btn-small">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
</tr>
</tbody>
</table>
</li>
</ul>
</div>
</div>
<section class="card-section" layout-mode>
<layout-selector></layout-selector>
<ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="plugin in ctrl.plugins">
<a class="card-item" href="plugins/{{plugin.id}}/edit">
<div class="card-item-header">
<i class="icon-gf icon-gf-{{plugin.type}}"></i>
{{plugin.type}}
</div>
<div class="card-item-body">
<figure class="card-item-figure">
<img ng-src="{{plugin.info.logos.small}}">
</figure>
<div class="card-item-details">
<div class="card-item-name" href="plugins/{{plugin.id}}/edit">{{plugin.name}}</div>
<div class="card-item-sub-name">By {{plugin.info.author.name}}</div>
</div>
</div>
</a>
</li>
</ol>
</section>
</div>

@ -11,6 +11,8 @@ export class PluginEditCtrl {
readmeHtml: any;
includedDatasources: any;
tabIndex: number;
tabs: any;
hasDashboards: any;
preUpdateHook: () => any;
postUpdateHook: () => any;
@ -19,6 +21,7 @@ export class PluginEditCtrl {
this.model = {};
this.pluginId = $routeParams.pluginId;
this.tabIndex = 0;
this.tabs = ['Overview'];
}
init() {
@ -35,15 +38,24 @@ export class PluginEditCtrl {
return plug;
});
if (this.model.type === 'app') {
this.tabs.push('Config');
this.hasDashboards = _.findWhere(result.includes, {type: 'dashboard'});
if (this.hasDashboards) {
this.tabs.push('Dashboards');
}
}
return this.initReadme();
});
}
initReadme() {
return this.$http.get(this.model.baseUrl + '/readme.md').then(res => {
return this.backendSrv.get(`/api/plugins/${this.pluginId}/readme`).then(res => {
return System.import('remarkable').then(Remarkable => {
var md = new Remarkable();
this.readmeHtml = this.$sce.trustAsHtml(md.render(res.data));
this.readmeHtml = this.$sce.trustAsHtml(md.render(res));
});
});
}
@ -54,6 +66,7 @@ export class PluginEditCtrl {
case 'panel': return 'icon-gf icon-gf-panel';
case 'app': return 'icon-gf icon-gf-apps';
case 'page': return 'icon-gf icon-gf-share';
case 'dashboard': return 'icon-gf icon-gf-dashboard';
}
}
@ -64,9 +77,7 @@ export class PluginEditCtrl {
// the next step of execution will block until the promise resolves.
// if the promise is rejected, this update will be aborted.
if (this.preUpdateHook != null) {
chain = chain.then(function() {
return Promise.resolve(self.preUpdateHook());
});
chain = self.preUpdateHook();
}
// Perform the core update procedure
@ -78,7 +89,7 @@ export class PluginEditCtrl {
secureJsonData: self.model.secureJsonData,
}, {});
return self.backendSrv.post(`/api/org/plugins/${self.pluginId}/settings`, updateCmd);
return self.backendSrv.post(`/api/plugins/${self.pluginId}/settings`, updateCmd);
});
// if set, performt he postUpdate hook. If a promise is returned it will block
@ -86,7 +97,7 @@ export class PluginEditCtrl {
// resolves. If the promise is rejected the page will not be reloaded.
if (this.postUpdateHook != null) {
chain = chain.then(function() {
return Promise.resolve(this.postUpdateHook());
return this.postUpdateHook();
});
}
@ -101,17 +112,16 @@ export class PluginEditCtrl {
this.preUpdateHook = callback;
}
setPOstUpdateHook(callback: () => any) {
setPostUpdateHook(callback: () => any) {
this.postUpdateHook = callback;
}
toggleEnabled() {
enable() {
this.model.enabled = true;
this.model.pinned = true;
this.update();
}
togglePinned() {
this.update();
}
}
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);

@ -4,11 +4,28 @@ import angular from 'angular';
export class PluginListCtrl {
plugins: any[];
tabIndex: number;
/** @ngInject */
constructor(private backendSrv: any) {
constructor(private backendSrv: any, $location) {
this.tabIndex = 0;
this.backendSrv.get('api/plugins', {embedded: 0}).then(plugins => {
var pluginType = $location.search().type || 'panel';
switch (pluginType) {
case "datasource": {
this.tabIndex = 1;
break;
}
case "app": {
this.tabIndex = 2;
break;
}
case "panel":
default:
this.tabIndex = 0;
}
this.backendSrv.get('api/plugins', {embedded: 0, type: pluginType}).then(plugins => {
this.plugins = plugins;
});
}

@ -11,7 +11,7 @@ export class AppPageCtrl {
/** @ngInject */
constructor(private backendSrv, private $routeParams: any, private $rootScope) {
this.pluginId = $routeParams.pluginId;
this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(app => {
this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(app => {
this.appModel = app;
this.page = _.findWhere(app.pages, {slug: this.$routeParams.slug});
if (!this.page) {

@ -43,6 +43,9 @@ function (angular, _) {
}
this.formatValue = function(value, format, variable) {
// for some scopedVars there is no variable
variable = variable || {};
if (typeof format === 'function') {
return format(value, variable, this.formatValue);
}
@ -66,6 +69,9 @@ function (angular, _) {
return '(' + quotedValues.join(' OR ') + ')';
}
case "pipe": {
if (typeof value === 'string') {
return value;
}
return value.join('|');
}
default: {
@ -126,9 +132,6 @@ function (angular, _) {
return target.replace(this._regex, function(match, g1, g2) {
variable = self._index[g1 || g2];
if (!variable) {
return match;
}
if (scopedVars) {
value = scopedVars[g1 || g2];
@ -137,6 +140,10 @@ function (angular, _) {
}
}
if (!variable) {
return match;
}
systemValue = self._grafanaVariables[variable.current.value];
if (systemValue) {
return self.formatValue(systemValue, format, variable);

@ -222,7 +222,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
.map(function(reservations) {
return _.pluck(reservations.Instances, targetAttributeName);
})
.flatten().value();
.flatten().uniq().sortBy().value();
return transformSuggestData(attributes);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

@ -4,5 +4,16 @@
"id": "cloudwatch",
"metrics": true,
"annotations": true
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/amazon-web-services.png",
"large": "img/amazon-web-services.png"
}
}
}

@ -66,7 +66,7 @@
<input class="tight-form-input" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
</li>
<li class="tight-form-item last">
<i class="fa fa-question-circle" bs-tooltip="'Trim the edges on the timeserie x datapoints'" data-placement="right"></i>
<i class="fa fa-question-circle" bs-tooltip="'Trim the edges on the timeseries x datapoints'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>

@ -6,7 +6,7 @@
"info": {
"description": "Elasticsearch Data Source for Grafana",
"author": {
"name": "Grafana Core",
"name": "Grafana Project",
"url": "http://grafana.org"
},
"keywords": ["elasticsearch"],

@ -0,0 +1,176 @@
{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource",
"pluginId": "graphite"
}
],
"revision": "1.0",
"title": "Graphite Carbon Metrics",
"tags": ["graphite", "carbon"],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapsable": true,
"collapse": false,
"editable": true,
"height": "350px",
"notice": false,
"panels": [
{
"aliasColors": {},
"annotate": {
"enable": false
},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"fill": 0,
"grid": {
"leftLogBase": 1,
"leftMax": null,
"leftMin": null,
"max": null,
"min": 0,
"rightLogBase": 1,
"rightMax": null,
"rightMin": null,
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 1,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"loadingEditor": false,
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"resolution": 100,
"scale": 1,
"seriesOverrides": [
{
"alias": "Points Per Update",
"yaxis": 2
},
{
"alias": "CPU",
"yaxis": 2
}
],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"target": "alias(sumSeries(carbon.agents.*.updateOperations),\"Updates\") "
},
{
"refId": "B",
"target": "alias(sumSeries(carbon.agents.*.metricsReceived),'Metrics Received')"
},
{
"refId": "C",
"target": "alias(sumSeries(carbon.agents.*.committedPoints),'Committed Points')"
},
{
"refId": "D",
"target": "alias(sumSeries(carbon.agents.*.pointsPerUpdate),'Points Per Update')"
},
{
"refId": "E",
"target": "alias(averageSeries(carbon.agents.*.cpuUsage),'CPU')"
},
{
"refId": "F",
"target": "alias(sumSeries(carbon.agents.*.creates),'Creates')"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Graphite Carbon Metrics",
"tooltip": {
"query_as_alias": true,
"shared": false,
"value_type": "cumulative"
},
"type": "graph",
"x-axis": true,
"y-axis": true,
"y_formats": [
"short",
"short"
],
"zerofill": true
}
],
"title": "Row1"
}
],
"time": {
"from": "now-3h",
"to": "now"
},
"timepicker": {
"collapse": false,
"enable": true,
"notice": false,
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"type": "timepicker"
},
"templating": {
"enable": false,
"list": []
},
"annotations": {
"enable": false,
"list": []
},
"refresh": false,
"schemaVersion": 8,
"version": 2,
"links": []
}

@ -1,21 +0,0 @@
{
"__inputs": {
"graphite": {
"type": "datasource",
"description": "Graphite datasource"
}
},
"title": "Carbon Cache Stats",
"version": 1,
"rows": [
{
"panels": [
{
"type": "graph",
"datasource": "__$graphite"
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

@ -4,9 +4,20 @@
"id": "graphite",
"includes": [
{"type": "dashboard", "name": "Carbon Cache Stats", "path": "dashboards/carbon_stats.json"}
{"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"}
],
"metrics": true,
"annotations": true
}
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/graphite_logo.png",
"large": "img/graphite_logo.png"
}
}
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="265px" viewBox="0 0 256 265" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M82.472546,256.199228 C75.4995714,246.636291 77.6910777,233.487253 87.2540143,226.514279 C89.2462927,225.119684 91.4377991,224.123545 93.6293054,223.326633 L94.0277611,215.357519 C88.8478371,213.166013 84.6640523,207.587633 84.2655966,202.009254 L76.4957106,200.016975 C75.1011157,202.009254 73.5072929,203.603076 71.5150144,204.997671 C61.9520778,211.970646 48.6038121,209.77914 41.8300654,200.216203 C34.8570907,190.653266 37.0485971,177.504229 46.6115337,170.531254 C56.1744703,163.558279 69.3235081,165.749786 76.2964827,175.312722 C78.0895334,177.902684 79.4841283,180.891102 80.0818118,183.87952 L93.6293054,187.465621 C97.2154066,186.071026 101.199964,185.871798 104.786065,187.266393 L112.555951,181.688013 C110.563672,173.519672 113.751318,164.554419 120.724292,159.573722 C130.287229,152.600748 143.635495,154.792254 150.409242,164.355191 C157.382216,173.7189 155.19071,187.067165 145.627773,194.04014 C138.654799,199.020836 129.29109,199.419292 122.118887,195.036279 L114.349001,200.614659 C114.548229,204.399988 113.153634,208.185317 110.7629,211.173735 L109.965989,225.119684 C112.755179,226.713507 115.145913,228.705785 116.938963,231.295747 C123.71271,240.858684 121.720432,254.007722 112.157495,260.980696 C102.793786,267.953671 89.4455206,265.762164 82.472546,256.199228 L82.472546,256.199228 Z" fill="#0690BA"></path>
<path d="M112.157495,234.68262 C109.965989,231.694203 106.977571,229.701924 103.789926,228.905013 L104.985293,208.583773 C108.172938,205.993811 109.368305,201.810026 107.97371,198.024697 L122.318115,187.664849 C127.498039,193.044001 136.064837,193.840912 142.2409,189.258672 C149.213874,184.277975 150.608469,174.715039 145.627773,167.742064 C140.647077,160.968317 131.08414,159.374495 124.111166,164.355191 C117.935103,168.937431 115.942824,177.305001 119.528925,183.87952 L104.985293,194.239368 C101.996875,191.649406 97.6138623,191.450178 94.0277611,193.641684 L74.3042043,188.46176 C74.5034321,185.074887 73.5072929,181.488786 71.5150144,178.699596 C66.5343183,171.925849 56.7721538,170.332026 49.9984071,175.312722 C43.0254324,180.293418 41.6308375,189.856355 46.6115337,196.82933 C51.5922298,203.802304 61.1551664,205.196899 68.1281411,200.216203 C70.7181031,198.223925 72.5111537,195.83319 73.5072929,193.044001 L90.8401155,197.626241 C89.4455206,200.415431 89.8439763,204.001532 91.8362547,206.591494 C93.8285332,209.380684 97.0161788,210.576051 100.203824,210.376823 L99.2076851,228.307329 C96.2192674,228.307329 93.2308497,229.303469 90.6408877,231.295747 C83.8671409,236.276443 82.2733181,245.83938 87.2540143,252.812354 C92.2347104,259.785329 101.797647,261.179924 108.770622,256.199228 C115.743596,251.218532 117.138191,241.655595 112.157495,234.68262 L112.157495,234.68262 Z M127.896495,169.734343 C131.881052,166.945153 137.260204,167.742064 140.049394,171.726621 C142.838583,175.711178 142.041672,181.09033 138.057115,183.87952 C134.86947,186.270254 130.486457,186.071026 127.498039,183.680292 L130.088001,181.688013 C131.881052,182.684153 134.471014,182.684153 136.264064,181.289558 C138.854026,179.496507 139.45171,175.910406 137.459432,173.320444 C135.666381,170.730482 132.08028,170.132798 129.490318,172.125077 C127.697267,173.519672 126.900356,175.711178 127.298811,177.902684 L124.708849,179.894963 C123.513482,176.308862 124.708849,172.125077 127.896495,169.734343 L127.896495,169.734343 Z M64.5420398,195.235507 C60.3582551,198.223925 54.5806475,197.227785 51.5922298,193.044001 C48.6038121,188.860216 49.5999514,183.082608 53.7837361,180.094191 C57.9675209,177.105773 63.7451284,178.101912 66.7335461,182.086469 C67.7296854,183.481064 68.3273689,185.074887 68.5265967,186.66871 L65.3389512,185.871798 C65.1397234,185.274115 64.9404955,184.477203 64.342812,183.87952 C62.3505335,181.09033 58.3659766,180.492646 55.5767867,182.484925 C52.7875969,184.477203 52.1899134,188.46176 54.1821918,191.25095 C56.1744703,194.04014 60.1590272,194.637823 62.9482171,192.645545 C63.7451284,192.047861 64.342812,191.25095 64.7412677,190.454039 L67.7296854,191.25095 C66.932774,192.844773 65.9366347,194.239368 64.5420398,195.235507 L64.5420398,195.235507 Z M92.2347104,249.226253 C89.2462927,245.042468 90.242432,239.264861 94.2269889,236.276443 C95.6215838,235.280304 97.2154066,234.68262 98.8092294,234.483393 L98.6100015,237.671038 C97.6138623,237.870266 96.8169509,238.069494 96.0200395,238.667177 C93.2308497,240.659456 92.6331661,244.644013 94.6254446,247.433203 C96.6177231,250.222392 100.60228,250.820076 103.39147,248.827798 C106.18066,246.835519 106.778343,242.850962 104.786065,240.061772 C104.387609,239.464089 103.789926,239.065633 103.192242,238.667177 L103.39147,235.280304 C104.786065,235.877988 106.18066,236.874127 107.176799,238.268722 C110.165217,242.452506 109.169077,248.230114 104.985293,251.218532 C101.000736,254.406177 95.2231281,253.410038 92.2347104,249.226253 L92.2347104,249.226253 Z" fill="#C3E8F2"></path>
<path d="M239.065633,223.924317 C227.908874,227.510418 215.955203,221.334355 212.369102,210.177595 C211.57219,207.786861 211.372962,205.595355 211.372962,203.204621 L203.802304,200.415431 C200.216203,204.798444 193.442456,206.790722 188.063304,205.396127 L183.680292,212.169874 C185.074887,213.962924 186.270254,216.154431 186.867937,218.545165 C190.454039,229.701924 184.277975,241.655595 173.121216,245.440924 C161.964457,249.027025 150.010786,242.850962 146.424685,231.694203 C142.838583,220.537443 149.014647,208.583773 160.171406,204.997671 C163.159824,204.001532 166.347469,203.802304 169.535115,204.20076 L177.105773,192.446317 C176.906545,188.660988 177.902684,184.875659 180.492646,181.887241 L177.504229,172.72276 C169.136659,172.125077 161.765229,166.148241 158.976039,158.179128 C155.389938,147.022368 161.566001,135.068697 172.72276,131.482596 C183.87952,127.896495 195.83319,134.072558 199.419292,145.229318 C202.009254,153.397659 199.419292,162.362912 193.044001,167.941292 L196.032418,177.105773 C199.817747,178.101912 203.005393,180.492646 204.997671,183.87952 L214.560608,188.46176 C216.951342,186.270254 223.127405,185.074887 226.115823,184.078748 C237.272582,180.492646 249.226253,186.66871 253.011582,197.825469 C256.398456,208.384545 250.222392,220.338216 239.065633,223.924317 L239.065633,223.924317 Z" fill="#0690BA"></path>
<path d="M227.709646,189.059444 C224.322772,190.254811 221.334355,192.446317 219.541304,195.235507 L200.415431,187.864077 C199.020836,184.078748 195.235507,181.688013 191.25095,181.887241 L185.871798,165.152102 C192.645545,161.765229 196.032418,153.995343 193.641684,146.623912 C191.051722,138.455571 182.285697,134.072558 174.316583,136.66252 C166.347469,139.252482 161.765229,148.018507 164.355191,155.987621 C166.745925,163.359052 174.117355,167.742064 181.488786,166.347469 L186.867937,183.281836 C183.481064,185.473342 181.887241,189.457899 183.082608,193.442456 L171.925849,210.576051 C168.738203,209.380684 165.152102,209.181456 161.765229,210.177595 C153.596887,212.767557 149.213874,221.533583 151.803836,229.502696 C154.393798,237.671038 163.159824,242.054051 171.128938,239.464089 C179.297279,236.874127 183.680292,228.108102 181.09033,220.138988 C180.094191,217.15057 178.30114,214.560608 175.910406,212.767557 L185.67257,197.626241 C188.063304,199.817747 191.450178,200.614659 194.637823,199.61852 C197.825469,198.62238 200.016975,196.032418 200.813887,192.844773 L217.549026,199.419292 C216.752114,202.208482 216.752114,205.396127 217.748254,208.384545 C220.338216,216.552886 229.104241,220.935899 237.073355,218.345937 C245.241696,215.755975 249.624709,206.98995 247.034747,199.020836 C244.444785,190.852494 235.877988,186.469482 227.709646,189.059444 L227.709646,189.059444 Z M170.92971,153.995343 C169.535115,149.413102 171.925849,144.432406 176.508089,143.037811 C181.09033,141.443988 186.071026,144.03395 187.465621,148.616191 C188.660988,152.40152 187.067165,156.585305 183.87952,158.577583 L182.88338,155.589166 C184.477203,154.194571 185.274115,151.803836 184.477203,149.61233 C183.481064,146.623912 180.293418,145.03009 177.305001,146.026229 C174.316583,147.022368 172.72276,150.210014 173.7189,153.198431 C174.515811,155.389938 176.308862,156.784533 178.500368,157.182988 L179.496507,160.370634 C175.711178,160.171406 172.125077,157.780672 170.92971,153.995343 L170.92971,153.995343 Z M175.51195,222.131266 C177.105773,227.111962 174.316583,232.291886 169.535115,233.885709 C164.554419,235.479532 159.374495,232.690342 157.780672,227.908874 C156.186849,223.127405 158.976039,217.748254 163.757507,216.154431 C165.35133,215.556747 167.144381,215.556747 168.738203,215.955203 L166.945153,218.744393 C166.347469,218.744393 165.550558,218.744393 164.753646,218.943621 C161.566001,219.93976 159.77295,223.525861 160.76909,226.912734 C161.765229,230.10038 165.35133,231.893431 168.738203,230.897291 C171.925849,229.901152 173.7189,226.315051 172.72276,223.127405 C172.324305,222.131266 171.925849,221.334355 171.128938,220.736671 L172.921988,218.146709 C173.918127,219.142848 174.914267,220.537443 175.51195,222.131266 L175.51195,222.131266 Z M235.280304,212.56833 C230.299608,214.162152 225.119684,211.372962 223.525861,206.591494 C222.928178,204.997671 222.928178,203.204621 223.326633,201.610798 L226.315051,202.806165 C226.115823,203.802304 226.115823,204.798444 226.514279,205.595355 C227.510418,208.982228 231.096519,210.576051 234.483393,209.579912 C237.870266,208.583773 239.663317,204.997671 238.46795,201.610798 C237.47181,198.423152 233.885709,196.630102 230.498836,197.626241 C229.901152,197.825469 229.104241,198.223925 228.705785,198.62238 L225.51814,197.427013 C226.514279,196.231646 227.908874,195.235507 229.701924,194.637823 C234.68262,193.044001 239.862544,195.83319 241.456367,200.614659 C242.850962,205.794583 240.261,210.974507 235.280304,212.56833 L235.280304,212.56833 Z" fill="#C3E8F2"></path>
<path d="M255.203089,66.7335461 C255.203089,78.4879891 245.640152,88.0509257 233.885709,88.0509257 C231.494975,88.0509257 229.104241,87.65247 223.326633,86.4571029 L218.345937,92.6331661 C224.721228,97.8130901 224.721228,104.786065 221.73281,109.567533 L226.713507,115.743596 C228.905013,114.946685 231.295747,114.548229 233.686481,114.548229 C245.440924,114.548229 255.003861,124.111166 255.003861,135.865609 C255.003861,147.620052 245.440924,157.182988 233.686481,157.182988 C221.932038,157.182988 212.369102,147.620052 212.369102,135.865609 C212.369102,132.677963 213.166013,129.490318 214.36138,126.701128 L205.595355,115.942824 C201.810026,114.946685 198.62238,112.755179 196.430874,109.567533 L186.867937,109.567533 C184.078748,117.337419 176.308862,122.517343 167.742064,122.517343 C155.987621,122.517343 146.424685,112.954406 146.424685,101.199964 C146.424685,89.4455206 155.987621,79.882584 167.742064,80.0818118 C176.308862,80.0818118 184.078748,85.4609637 187.266393,93.2308497 L196.82933,93.2308497 C199.020836,90.0432041 202.208482,87.65247 205.993811,86.8555586 L214.759836,76.0972549 C213.365241,73.3080651 212.767557,70.1204195 212.767557,66.932774 C212.767557,55.1783311 222.330494,45.6153944 234.084937,45.6153944 C245.83938,45.6153944 255.203089,55.1783311 255.203089,66.7335461 L255.203089,66.7335461 Z" fill="#0690BA"></path>
<path d="M218.545165,66.7335461 C218.545165,70.3196474 219.740532,73.7065207 221.932038,76.2964827 L208.982228,92.0354826 C204.997671,92.2347104 201.41157,95.0239003 200.415431,98.8092294 L182.684153,98.8092294 C181.488786,91.4377991 175.113494,85.6601915 167.542836,85.6601915 C158.976039,85.6601915 152.202292,92.4339383 152.202292,101.000736 C152.202292,109.368305 158.976039,116.34128 167.542836,116.34128 C175.312722,116.34128 181.688013,110.563672 182.88338,103.192242 L200.614659,103.192242 C201.610798,106.977571 204.997671,109.766761 209.181456,109.965989 L222.131266,125.704989 C219.93976,128.294951 218.744393,131.681824 218.744393,135.267925 C218.744393,143.834723 225.717367,150.608469 234.084937,150.608469 C242.651734,150.608469 249.425481,143.635495 249.425481,135.267925 C249.425481,126.701128 242.651734,119.927381 234.084937,119.927381 C230.897291,119.927381 227.908874,120.92352 225.51814,122.517343 L214.162152,108.571394 C216.951342,106.977571 218.744393,103.989153 218.744393,100.801508 C218.744393,97.4146344 216.951342,94.4262168 214.162152,92.832394 L225.51814,78.8864447 C227.908874,80.4802675 230.897291,81.4764067 234.084937,81.4764067 C242.651734,81.4764067 249.425481,74.5034321 249.425481,66.1358626 C249.425481,57.5690652 242.452506,50.7953184 234.084937,50.7953184 C225.51814,51.393002 218.545165,58.3659766 218.545165,66.7335461 L218.545165,66.7335461 Z M167.742064,109.965989 C162.960596,109.965989 158.976039,105.981432 158.976039,101.199964 C158.976039,96.4184952 162.960596,92.4339383 167.742064,92.4339383 C171.726621,92.4339383 175.113494,95.2231281 176.109634,98.8092294 L172.921988,98.8092294 C172.125077,96.8169509 170.132798,95.422356 167.742064,95.422356 C164.554419,95.422356 162.163684,98.012318 162.163684,101.000736 C162.163684,104.188381 164.753646,106.778343 167.742064,106.778343 C170.132798,106.778343 172.125077,105.383748 172.921988,103.39147 L176.109634,103.39147 C175.113494,107.376027 171.726621,109.965989 167.742064,109.965989 L167.742064,109.965989 Z M233.885709,126.701128 C239.065633,126.701128 243.249418,130.884913 243.249418,136.064837 C243.249418,141.244761 239.065633,145.428545 233.885709,145.428545 C228.705785,145.428545 224.522,141.244761 224.522,136.064837 C224.522,134.271786 225.119684,132.677963 225.916595,131.283368 L228.108102,133.87333 C227.908874,134.471014 227.709646,135.267925 227.709646,136.064837 C227.709646,139.45171 230.498836,142.2409 233.885709,142.2409 C237.272582,142.2409 240.061772,139.45171 240.061772,136.064837 C240.061772,132.677963 237.272582,129.888773 233.885709,129.888773 C232.88957,129.888773 231.893431,130.088001 231.096519,130.486457 L229.104241,128.095723 C230.498836,127.099584 232.291886,126.701128 233.885709,126.701128 L233.885709,126.701128 Z M243.249418,66.7335461 C243.249418,71.9134701 239.065633,76.0972549 233.885709,76.0972549 C232.092658,76.0972549 230.498836,75.6987992 229.104241,74.70266 L231.096519,72.3119258 C231.893431,72.7103815 232.88957,72.9096094 233.885709,73.1088372 C237.272582,73.1088372 240.061772,70.3196474 240.061772,66.932774 C240.061772,63.5459006 237.272582,60.7567107 233.885709,60.7567107 C230.498836,60.7567107 227.709646,63.5459006 227.709646,66.932774 C227.709646,67.7296854 227.908874,68.3273689 228.108102,68.9250524 L225.916595,71.5150144 C225.119684,70.1204195 224.522,68.5265967 224.522,66.7335461 C224.522,61.5536221 228.705785,57.3698374 233.885709,57.3698374 C239.065633,57.5690652 243.249418,61.5536221 243.249418,66.7335461 L243.249418,66.7335461 Z" fill="#C3E8F2"></path>
<path d="M110.7629,1.98449612 C121.91966,5.57059736 128.095723,17.723496 124.310394,28.8802554 C123.513482,31.2709895 122.517343,33.263268 120.92352,35.2555464 L125.306533,42.0292932 C130.884913,40.6346983 137.459432,42.8262046 141.045533,47.2092172 L148.616191,44.4200274 C148.616191,42.0292932 149.014647,39.6385591 149.61233,37.4470527 C153.198431,26.2902934 165.35133,20.1142301 176.508089,23.8995592 C187.664849,27.4856604 193.840912,39.4393312 190.055583,50.7953184 C186.469482,61.9520778 174.316583,68.1281411 163.159824,64.342812 C160.171406,63.3466727 157.382216,61.75285 155.19071,59.5613437 L142.2409,64.5420398 C140.248621,67.7296854 137.060976,70.3196474 133.275647,71.3157866 L130.287229,80.4802675 C136.66252,86.0586472 139.252482,95.0239003 136.66252,103.192242 C133.076419,114.349001 120.92352,120.525065 109.766761,116.739736 C98.8092294,112.555951 92.6331661,100.60228 96.2192674,89.4455206 C98.8092294,81.2771789 106.379888,75.4995714 114.747457,74.9018878 L117.735875,65.7374069 C115.345141,62.7489892 114.149774,58.9636601 114.548229,54.9791032 L106.977571,43.2246603 C103.789926,43.623116 100.60228,43.4238881 97.6138623,42.4277489 C86.4571029,38.8416477 80.2810397,26.8879769 83.8671409,15.7312175 C87.65247,4.57445812 99.6061408,-1.60160511 110.7629,1.98449612 L110.7629,1.98449612 Z" fill="#0690BA"></path>
<path d="M99.4069129,36.8493692 C102.793786,38.0447363 106.379888,37.8455084 109.567533,36.6501414 L120.525065,53.7837361 C119.329698,57.7682931 121.122748,61.75285 124.310394,63.9443563 L118.732014,80.6794954 C111.360584,79.4841283 103.989153,83.6679131 101.598419,91.0393434 C99.0084572,99.2076851 103.39147,107.774482 111.360584,110.563672 C119.329698,113.153634 128.095723,108.770622 130.884913,100.801508 C133.275647,93.4300775 129.888773,85.6601915 123.115027,82.2733181 L128.693406,65.5381791 C132.677963,65.7374069 136.463292,63.3466727 137.857887,59.5613437 L156.98376,52.1899134 C158.776811,54.9791032 161.566001,57.3698374 165.152102,58.3659766 C173.320444,60.9559386 181.887241,56.572926 184.676431,48.4045843 C187.266393,40.2362426 182.88338,31.6694452 174.715039,28.8802554 C166.546697,26.2902934 157.9799,30.673306 155.19071,38.6424198 C154.194571,41.6308375 154.194571,44.8184831 154.991482,47.6076729 L138.256343,54.1821918 C137.658659,51.1937741 135.467153,48.4045843 132.279508,47.4084451 C129.091862,46.4123058 125.704989,47.2092172 123.314254,49.2014957 L113.55209,34.0601794 C115.942824,32.2671287 117.735875,29.6771667 118.732014,26.6887491 C121.321976,18.5204074 116.938963,9.95360997 108.770622,7.36364797 C100.60228,4.77368597 92.0354826,9.15669859 89.4455206,17.3250403 C86.8555586,25.493382 91.2385712,34.2594072 99.4069129,36.8493692 L99.4069129,36.8493692 Z M124.708849,98.8092294 C123.115027,103.39147 118.333558,105.981432 113.751318,104.387609 C109.169077,102.793786 106.579115,98.012318 108.172938,93.4300775 C109.368305,89.6447484 113.153634,87.2540143 116.938963,87.4532421 L115.942824,90.4416598 C113.751318,90.6408877 111.958267,92.2347104 111.161356,94.2269889 C110.165217,97.2154066 111.759039,100.403052 114.747457,101.399191 C117.735875,102.395331 120.92352,100.801508 121.91966,97.8130901 C122.716571,95.6215838 121.91966,93.2308497 120.325837,91.8362547 L121.321976,88.6486092 C124.509622,90.8401155 126.103444,94.8246724 124.708849,98.8092294 L124.708849,98.8092294 Z M160.968317,40.8339261 C162.56214,35.85323 167.742064,33.263268 172.72276,34.8570907 C177.504229,36.4509135 180.293418,41.8300654 178.699596,46.6115337 C177.105773,51.393002 171.925849,54.1821918 166.945153,52.5883691 C165.35133,51.9906855 163.956735,50.9945463 162.960596,49.7991792 L166.148241,48.6038121 C166.745925,49.0022678 167.343608,49.4007235 168.14052,49.5999514 C171.328165,50.5960906 174.914267,48.80304 175.910406,45.6153944 C176.906545,42.4277489 175.113494,38.8416477 171.925849,37.8455084 C168.738203,36.8493692 165.152102,38.6424198 163.956735,41.8300654 C163.757507,42.8262046 163.558279,43.8223438 163.757507,44.6192552 L160.76909,45.8146223 C160.569862,44.2207995 160.569862,42.4277489 160.968317,40.8339261 L160.968317,40.8339261 Z M106.977571,13.3404834 C111.958267,14.9343061 114.548229,20.1142301 112.954406,25.0949263 C112.356723,26.6887491 111.559812,28.083344 110.165217,29.2787111 L108.571394,26.6887491 C109.169077,26.0910655 109.766761,25.2941541 110.165217,24.2980149 C111.161356,20.9111415 109.368305,17.5242681 106.18066,16.328901 C102.993014,15.3327618 99.4069129,17.1258124 98.2115458,20.313458 C97.2154066,23.5011035 99.0084572,27.0872047 102.196103,28.083344 C102.793786,28.2825718 103.590698,28.4817997 104.188381,28.2825718 L105.981432,31.0717617 C104.387609,31.4702174 102.793786,31.4702174 101.000736,30.8725338 C96.0200395,29.2787111 93.4300775,24.098787 95.0239003,19.1180909 C96.8169509,14.5358504 102.196103,11.7466606 106.977571,13.3404834 L106.977571,13.3404834 Z" fill="#C3E8F2"></path>
<path d="M34.6578629,114.149774 C36.6501414,115.544368 38.2439641,117.337419 39.6385591,119.329698 L47.4084451,117.337419 C47.8069007,111.759039 51.9906855,105.981432 57.1706095,103.989153 L56.7721538,96.0200395 C54.5806475,95.422356 52.3891412,94.2269889 50.3968627,92.832394 C41.033154,85.6601915 38.8416477,72.3119258 45.8146223,62.7489892 C52.7875969,53.1860526 65.9366347,51.1937741 75.4995714,58.1667487 C84.8632801,65.1397234 87.0547864,78.2887612 80.0818118,87.8516978 C78.2887612,90.4416598 75.8980271,92.4339383 73.1088372,94.0277611 L73.9057486,107.97371 C76.2964827,110.962128 77.6910777,114.747457 77.4918498,118.532786 L85.2617358,124.111166 C92.4339383,119.728153 101.797647,120.126609 108.770622,125.107305 C118.333558,132.08028 120.325837,145.428545 113.352862,154.792254 C106.579115,164.355191 93.2308497,166.347469 83.6679131,159.573722 C76.6949384,154.593026 73.5072929,145.627773 75.4995714,137.459432 L67.7296854,131.881052 C64.1435841,133.275647 60.1590272,133.275647 56.572926,131.681824 L43.0254324,135.267925 C42.4277489,138.455571 41.2323818,141.443988 39.2401034,143.834723 C32.4663566,153.198431 19.3173187,155.389938 9.75438212,148.416963 C0.191445508,141.643216 -1.80083295,128.294951 4.97291382,118.732014 C11.9458884,109.169077 25.0949263,107.176799 34.6578629,114.149774 Z" fill="#0690BA"></path>
<path d="M34.6578629,140.447849 C36.8493692,137.459432 37.8455084,134.072558 37.6462806,130.685685 L57.3698374,125.505761 C60.7567107,127.697267 65.1397234,127.498039 68.3273689,124.908077 L82.6717738,135.267925 C79.2849004,141.842444 81.0779511,150.210014 87.2540143,154.792254 C94.2269889,159.77295 103.789926,158.378355 108.770622,151.405381 C113.751318,144.631634 112.356723,134.86947 105.383748,129.888773 C99.2076851,125.306533 90.6408877,126.103444 85.2617358,131.482596 L70.9173309,121.122748 C72.3119258,117.536647 71.3157866,113.153634 68.1281411,110.563672 L66.932774,90.242432 C70.3196474,89.4455206 73.3080651,87.4532421 75.3003435,84.4648244 C80.2810397,77.4918498 78.6872169,67.9289132 71.9134701,62.9482171 C65.1397234,57.9675209 55.3775589,59.3621158 50.3968627,66.3350904 C45.4161666,73.1088372 46.8107615,82.8710017 53.7837361,87.8516978 C56.3736981,89.6447484 59.3621158,90.6408877 62.3505335,90.8401155 L63.3466727,108.770622 C60.1590272,108.372166 56.9713817,109.766761 54.9791032,112.555951 C52.9868247,115.345141 52.7875969,118.732014 53.982964,121.521204 L36.6501414,126.103444 C35.6540021,123.314254 33.8609515,120.724292 31.2709895,118.931242 C24.2980149,113.950546 14.7350783,115.544368 9.75438212,122.318115 C4.77368597,129.091862 6.16828089,138.854026 13.1412555,143.834723 C20.1142301,148.815419 29.6771667,147.221596 34.6578629,140.447849 L34.6578629,140.447849 Z M101.399191,135.267925 C105.383748,138.057115 106.18066,143.635495 103.39147,147.420824 C100.60228,151.405381 95.0239003,152.202292 91.2385712,149.413102 C88.0509257,147.022368 86.8555586,142.838583 88.2501535,139.252482 L90.8401155,141.244761 C90.4416598,143.436267 91.2385712,145.627773 93.0316218,147.022368 C95.6215838,148.815419 99.2076851,148.217735 101.000736,145.827001 C102.793786,143.237039 102.395331,139.650938 99.8053686,137.857887 C98.012318,136.463292 95.422356,136.463292 93.6293054,137.459432 L90.6408877,135.467153 C93.8285332,133.076419 98.012318,132.877191 101.399191,135.267925 L101.399191,135.267925 Z M57.3698374,82.8710017 C53.1860526,79.882584 52.3891412,73.9057486 55.3775589,69.9211917 C58.3659766,65.7374069 64.342812,64.9404955 68.3273689,67.9289132 C72.5111537,70.9173309 73.5072929,76.8941663 70.3196474,80.8787232 C69.3235081,82.2733181 67.9289132,83.2694574 66.5343183,83.8671409 L66.3350904,80.4802675 C66.932774,80.0818118 67.5304575,79.6833561 67.9289132,79.0856726 C69.9211917,76.2964827 69.3235081,72.3119258 66.5343183,70.3196474 C63.7451284,68.3273689 59.7605715,68.9250524 57.7682931,71.7142423 C55.7760146,74.5034321 56.3736981,78.4879891 59.162888,80.4802675 C59.9597994,81.0779511 60.9559386,81.4764067 61.75285,81.4764067 L61.9520778,84.6640523 C60.5574829,84.4648244 58.9636601,83.8671409 57.3698374,82.8710017 L57.3698374,82.8710017 Z M14.7350783,125.904216 C17.723496,121.720432 23.7003314,120.724292 27.6848883,123.911938 C29.0794832,124.908077 30.0756224,126.302672 30.8725338,127.697267 L27.6848883,128.494178 C27.2864326,127.697267 26.6887491,126.900356 25.8918377,126.302672 C23.1026478,124.310394 19.1180909,124.908077 17.1258124,127.697267 C15.133534,130.486457 15.7312175,134.471014 18.5204074,136.463292 C21.3095972,138.455571 25.2941541,137.857887 27.2864326,135.068697 C27.6848883,134.471014 28.083344,133.87333 28.2825718,133.076419 L31.6694452,132.279508 C31.4702174,133.87333 31.0717617,135.467153 29.8763946,136.861748 C26.8879769,141.045533 21.1103694,141.842444 16.9265846,138.854026 C12.543572,135.865609 11.7466606,129.888773 14.7350783,125.904216 L14.7350783,125.904216 Z" fill="#C3E8F2"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

@ -5,5 +5,16 @@
"defaultMatchFormat": "regex values",
"metrics": true,
"annotations": true
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/influxdb_logo.svg",
"large": "img/influxdb_logo.svg"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -5,5 +5,16 @@
"metrics": true,
"defaultMatchFormat": "pipe",
"annotations": true
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/opentsdb_logo.png",
"large": "img/opentsdb_logo.png"
}
}
}

@ -0,0 +1,577 @@
{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource",
"pluginId": "prometheus"
}
],
"revision": "1.0",
"title": "Prometheus Stats",
"tags": ["prometheus"],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": true,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": 178,
"panels": [
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"datasource": "${DS_NAME}",
"decimals": 1,
"editable": true,
"error": false,
"format": "s",
"id": 5,
"interval": null,
"links": [],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"span": 3,
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
},
"targets": [
{
"expr": "(time() - process_start_time_seconds{job=\"prometheus\"})",
"intervalFactor": 2,
"refId": "A"
}
],
"thresholds": "",
"title": "Uptime",
"type": "singlestat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "current"
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"rgba(50, 172, 45, 0.97)",
"rgba(237, 129, 40, 0.89)",
"rgba(245, 54, 54, 0.9)"
],
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"format": "none",
"id": 6,
"interval": null,
"links": [],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"span": 3,
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"targets": [
{
"expr": "prometheus_local_storage_memory_series",
"intervalFactor": 2,
"refId": "A"
}
],
"thresholds": "0,1,5",
"title": "Local Storage Memory Series",
"type": "singlestat",
"valueFontSize": "70%",
"valueMaps": [],
"valueName": "current"
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": true,
"colors": [
"rgba(50, 172, 45, 0.97)",
"rgba(237, 129, 40, 0.89)",
"rgba(245, 54, 54, 0.9)"
],
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"format": "none",
"id": 7,
"interval": null,
"links": [],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"span": 3,
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"targets": [
{
"expr": "prometheus_local_storage_indexing_queue_length",
"intervalFactor": 2,
"refId": "A"
}
],
"thresholds": "0,500,4000",
"title": "Interal Storage Queue Length",
"type": "singlestat",
"valueFontSize": "70%",
"valueMaps": [
{
"op": "=",
"text": "Empty",
"value": "0"
}
],
"valueName": "current"
},
{
"content": "<img src=\"http://prometheus.io/assets/prometheus_logo_grey.svg\" alt=\"Prometheus logo\" style=\"height: 40px;\">\n<span style=\"font-family: 'Open Sans', 'Helvetica Neue', Helvetica; font-size: 25px;vertical-align: text-top;color: #bbbfc2;margin-left: 10px;\">Prometheus</span>\n\n<p style=\"margin-top: 10px;\">You're using Prometheus, an open-source systems monitoring and alerting toolkit originally built at SoundCloud. For more information, check out the <a href=\"http://www.grafana.org/\">Grafana</a> and <a href=\"http://prometheus.io/\">Prometheus</a> projects.</p>",
"editable": true,
"error": false,
"id": 9,
"links": [],
"mode": "html",
"span": 3,
"style": {},
"title": "",
"transparent": true,
"type": "text"
}
],
"title": "New row"
},
{
"collapse": false,
"editable": true,
"height": 227,
"panels": [
{
"aliasColors": {
"prometheus": "#C15C17",
"{instance=\"localhost:9090\",job=\"prometheus\"}": "#C15C17"
},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"leftLogBase": 1,
"leftMax": null,
"leftMin": null,
"rightLogBase": 1,
"rightMax": null,
"rightMin": null,
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 3,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 9,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(prometheus_local_storage_ingested_samples_total[5m])",
"interval": "",
"intervalFactor": 2,
"legendFormat": "{{job}}",
"metric": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Samples ingested (rate-5m)",
"tooltip": {
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"x-axis": true,
"y-axis": true,
"y_formats": [
"short",
"short"
]
},
{
"content": "#### Samples Ingested\nThis graph displays the count of samples ingested by the Prometheus server, as measured over the last 5 minutes, per time series in the range vector. When troubleshooting an issue on IRC or Github, this is often the first stat requested by the Prometheus team. ",
"editable": true,
"error": false,
"id": 8,
"links": [],
"mode": "markdown",
"span": 2.995914043583536,
"style": {},
"title": "",
"transparent": true,
"type": "text"
}
],
"title": "New row"
},
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {
"prometheus": "#F9BA8F",
"{instance=\"localhost:9090\",interval=\"5s\",job=\"prometheus\"}": "#F9BA8F"
},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"leftLogBase": 1,
"leftMax": null,
"leftMin": null,
"rightLogBase": 1,
"rightMax": null,
"rightMin": null,
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 5,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(prometheus_target_interval_length_seconds_count[5m])",
"intervalFactor": 2,
"legendFormat": "{{job}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Target Scrapes (last 5m)",
"tooltip": {
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"x-axis": true,
"y-axis": true,
"y_formats": [
"short",
"short"
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"leftLogBase": 1,
"leftMax": null,
"leftMin": null,
"rightLogBase": 1,
"rightMax": null,
"rightMin": null,
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 14,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "prometheus_target_interval_length_seconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
"interval": "",
"intervalFactor": 2,
"legendFormat": "{{quantile}} ({{interval}})",
"metric": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Scrape Duration",
"tooltip": {
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"x-axis": true,
"y-axis": true,
"y_formats": [
"short",
"short"
]
},
{
"content": "#### Scrapes\nPrometheus scrapes metrics from instrumented jobs, either directly or via an intermediary push gateway for short-lived jobs. Target scrapes will show how frequently targets are scraped, as measured over the last 5 minutes, per time series in the range vector. Scrape Duration will show how long the scrapes are taking, with percentiles available as series. ",
"editable": true,
"error": false,
"id": 11,
"links": [],
"mode": "markdown",
"span": 3,
"style": {},
"title": "",
"transparent": true,
"type": "text"
}
],
"title": "New row"
},
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"decimals": null,
"editable": true,
"error": false,
"fill": 1,
"grid": {
"leftLogBase": 1,
"leftMax": null,
"leftMin": null,
"rightLogBase": 1,
"rightMax": null,
"rightMin": null,
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 12,
"leftYAxisLabel": "",
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"hideEmpty": true,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 9,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "prometheus_evaluator_duration_milliseconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
"interval": "",
"intervalFactor": 2,
"legendFormat": "{{quantile}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Rule Eval Duration",
"tooltip": {
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"x-axis": true,
"y-axis": true,
"y_formats": [
"percentunit",
"short"
]
},
{
"content": "#### Rule Evaluation Duration\nThis graph panel plots the duration for all evaluations to execute. The 50th percentile, 90th percentile and 99th percentile are shown as three separate series to help identify outliers that may be skewing the data.",
"editable": true,
"error": false,
"id": 15,
"links": [],
"mode": "markdown",
"span": 3,
"style": {},
"title": "",
"transparent": true,
"type": "text"
}
],
"title": "New row"
}
],
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": false,
"schemaVersion": 7,
"links": [
{
"icon": "info",
"tags": [],
"targetBlank": true,
"title": "Grafana Docs",
"tooltip": "",
"type": "link",
"url": "http://www.grafana.org/docs"
},
{
"icon": "info",
"tags": [],
"targetBlank": true,
"title": "Prometheus Docs",
"type": "link",
"url": "http://prometheus.io/docs/introduction/overview/"
}
]
}

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="115.333px" height="114px" viewBox="0 0 115.333 114" enable-background="new 0 0 115.333 114" xml:space="preserve">
<g id="Layer_2">
</g>
<g>
<path fill="#FFFFFF" d="M56.667,0.667C25.372,0.667,0,26.036,0,57.332c0,31.295,25.372,56.666,56.667,56.666
s56.666-25.371,56.666-56.666C113.333,26.036,87.961,0.667,56.667,0.667z M56.667,106.722c-8.904,0-16.123-5.948-16.123-13.283
H72.79C72.79,100.773,65.571,106.722,56.667,106.722z M83.297,89.04H30.034v-9.658h53.264V89.04z M83.106,74.411h-52.92
c-0.176-0.203-0.356-0.403-0.526-0.609c-5.452-6.62-6.736-10.076-7.983-13.598c-0.021-0.116,6.611,1.355,11.314,2.413
c0,0,2.42,0.56,5.958,1.205c-3.397-3.982-5.414-9.044-5.414-14.218c0-11.359,8.712-21.285,5.569-29.308
c3.059,0.249,6.331,6.456,6.552,16.161c3.252-4.494,4.613-12.701,4.613-17.733c0-5.21,3.433-11.262,6.867-11.469
c-3.061,5.045,0.793,9.37,4.219,20.099c1.285,4.03,1.121,10.812,2.113,15.113C63.797,33.534,65.333,20.5,71,16
c-2.5,5.667,0.37,12.758,2.333,16.167c3.167,5.5,5.087,9.667,5.087,17.548c0,5.284-1.951,10.259-5.242,14.148
c3.742-0.702,6.326-1.335,6.326-1.335l12.152-2.371C91.657,60.156,89.891,67.418,83.106,74.411z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -3,6 +3,21 @@
"name": "Prometheus",
"id": "prometheus",
"includes": [
{"type": "dashboard", "name": "Prometheus Stats", "path": "dashboards/prometheus_stats.json"}
],
"metrics": true,
"annotations": true
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/prometheus_logo.svg",
"large": "img/prometheus_logo.svg"
}
}
}

@ -43,13 +43,10 @@ class DashListCtrl extends PanelCtrl {
var params: any = {limit: this.panel.limit};
if (this.panel.mode === 'recently viewed') {
var dashboardIds = impressions.getDashboardOpened();
var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit);
return this.backendSrv.search({
dashboardIds: impressions.getDashboardOpened(),
limit: this.panel.limit
}).then(result => {
this.dashList = dashboardIds.map(orderId => {
return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => {
this.dashList = dashIds.map(orderId => {
return _.find(result, dashboard => {
return dashboard.id === orderId;
});

@ -1,5 +1,12 @@
{
"type": "panel",
"name": "Dashboard list",
"id": "dashlist"
"id": "dashlist",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
}
}
}

@ -1,5 +1,12 @@
{
"type": "panel",
"name": "Graph",
"id": "graph"
"id": "graph",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
}
}
}

@ -1,5 +1,12 @@
{
"type": "panel",
"name": "Singlestat",
"id": "singlestat"
"id": "singlestat",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
}
}
}

@ -1,5 +1,12 @@
{
"type": "panel",
"name": "Table",
"id": "table"
"id": "table",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
}
}
}

@ -1,5 +1,12 @@
{
"type": "panel",
"name": "Text",
"id": "text"
"id": "text",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save