diff --git a/' b/'
new file mode 100644
index 00000000000..2e342921a63
--- /dev/null
+++ b/'
@@ -0,0 +1,200 @@
+
+/** Created by: Alex Wendland (me@alexwendland.com), 2014-08-06
+ *
+ * angular-json-tree
+ *
+ * Directive for creating a tree-view out of a JS Object. Only loads
+ * sub-nodes on demand in order to improve performance of rendering large
+ * objects.
+ *
+ * Attributes:
+ * - object (Object, 2-way): JS object to build the tree from
+ * - start-expanded (Boolean, 1-way, ?=true): should the tree default to expanded
+ *
+ * Usage:
+ * // In the controller
+ * scope.someObject = {
+ * test: 'hello',
+ * array: [1,1,2,3,5,8]
+ * };
+ * // In the html
+ *
+ *
+ * Dependencies:
+ * - utils (json-tree.js)
+ * - ajsRecursiveDirectiveHelper (json-tree.js)
+ *
+ * Test: json-tree-test.js
+ */
+
+import angular from 'angular';
+import coreModule from 'app/core/core_module';
+
+var utils = {
+ /* See link for possible type values to check against.
+ * http://stackoverflow.com/questions/4622952/json-object-containing-array
+ *
+ * Value Class Type
+ * -------------------------------------
+ * "foo" String string
+ * new String("foo") String object
+ * 1.2 Number number
+ * new Number(1.2) Number object
+ * true Boolean boolean
+ * new Boolean(true) Boolean object
+ * new Date() Date object
+ * new Error() Error object
+ * [1,2,3] Array object
+ * new Array(1, 2, 3) Array object
+ * new Function("") Function function
+ * /abc/g RegExp object (function in Nitro/V8)
+ * new RegExp("meow") RegExp object (function in Nitro/V8)
+ * {} Object object
+ * new Object() Object object
+ */
+ is: function is(obj, clazz) {
+ return Object.prototype.toString.call(obj).slice(8, -1) === clazz;
+ },
+
+ // See above for possible values
+ whatClass: function whatClass(obj) {
+ return Object.prototype.toString.call(obj).slice(8, -1);
+ },
+
+ // Iterate over an objects keyset
+ forKeys: function forKeys(obj, f) {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key) && typeof obj[key] !== 'function') {
+ if (f(key, obj[key])) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+coreModule.directive('jsonTree', [function jsonTreeDirective() {
+ return {
+ restrict: 'E',
+ scope: {
+ object: '=',
+ startExpanded: '=',
+ rootName: '@',
+ },
+ template: ''
+ };
+}]);
+
+coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNodeDirective(ajsRecursiveDirectiveHelper) {
+ return {
+ restrict: 'E',
+ scope: {
+ key: '=',
+ value: '=',
+ startExpanded: '='
+ },
+ compile: function jsonNodeDirectiveCompile(elem) {
+ return ajsRecursiveDirectiveHelper.compile(elem, this);
+ },
+ template: ' {{key}}' +
+ ' {{value}}' +
+ ' ' +
+ ' {preview}}' +
+ '
' +
+ ' - ' +
+ ' ' +
+ '
' +
+ '
',
+ pre: function jsonNodeDirectiveLink(scope, elem, attrs) {
+ // Set value's type as Class for CSS styling
+ elem.addClass(utils.whatClass(scope.value).toLowerCase());
+ // If the value is an Array or Object, use expandable view type
+ if (utils.is(scope.value, 'Object') || utils.is(scope.value, 'Array')) {
+ scope.isExpandable = true;
+ // Add expandable class for CSS usage
+ elem.addClass('expandable');
+ // Setup preview text
+ var isArray = utils.is(scope.value, 'Array');
+ scope.preview = isArray ? '[ ' : '{ ';
+ utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) {
+ if (isArray) {
+ scope.preview += value + ', ';
+ } else {
+ scope.preview += key + ': ' + value + ', ';
+ }
+ });
+ scope.preview = scope.preview.substring(0, scope.preview.length - (scope.preview.length > 2 ? 2 : 0)) + (isArray ? ' ]' : ' }');
+ // If directive initially has isExpanded set, also set shouldRender to true
+ if (scope.startExpanded) {
+ scope.shouldRender = true;
+ elem.addClass('expanded');
+ }
+ // Setup isExpanded state handling
+ scope.isExpanded = scope.startExpanded ? scope.startExpanded() : false;
+ scope.toggleExpanded = function jsonNodeDirectiveToggleExpanded() {
+ scope.isExpanded = !scope.isExpanded;
+ if (scope.isExpanded) {
+ elem.addClass('expanded');
+ } else {
+ elem.removeClass('expanded');
+ }
+ // For delaying subnode render until requested
+ scope.shouldRender = true;
+ };
+ } else {
+ scope.isExpandable = false;
+ // Add expandable class for CSS usage
+ elem.addClass('not-expandable');
+ }
+ }
+ };
+}]);
+
+/** Added by: Alex Wendland (me@alexwendland.com), 2014-08-09
+ * Source: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives
+ *
+ * Used to allow for recursion within directives
+ */
+coreModule.factory('ajsRecursiveDirectiveHelper', ['$compile', function RecursiveDirectiveHelper($compile) {
+ return {
+ /**
+ * Manually compiles the element, fixing the recursion loop.
+ * @param element
+ * @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
+ * @returns An object containing the linking functions.
+ */
+ compile: function RecursiveDirectiveHelperCompile(element, link) {
+ // Normalize the link parameter
+ if (angular.isFunction(link)) {
+ link = {
+ post: link
+ };
+ }
+
+ // Break the recursion loop by removing the contents
+ var contents = element.contents().remove();
+ var compiledContents;
+ return {
+ pre: (link && link.pre) ? link.pre : null,
+ /**
+ * Compiles and re-adds the contents
+ */
+ post: function RecursiveDirectiveHelperCompilePost(scope, element) {
+ // Compile the contents
+ if (!compiledContents) {
+ compiledContents = $compile(contents);
+ }
+ // Re-add the compiled contents to the element
+ compiledContents(scope, function (clone) {
+ element.append(clone);
+ });
+
+ // Call the post-linking function, if any
+ if (link && link.post) {
+ link.post.apply(null, arguments);
+ }
+ }
+ };
+ }
+ };
+}]);
diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go
index 579813e3c0e..2276d87e545 100644
--- a/pkg/api/dtos/alerting.go
+++ b/pkg/api/dtos/alerting.go
@@ -42,8 +42,8 @@ type AlertTestCommand struct {
type AlertTestResult struct {
Triggered bool `json:"triggerd"`
Timing string `json:"timing"`
- Error string `json:"error"`
- Logs []*AlertTestResultLog `json:"logs"`
+ Error string `json:"error,omitempty"`
+ Logs []*AlertTestResultLog `json:"logs,omitempty"`
}
type AlertTestResultLog struct {
diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go
index 29e0ff2cd32..2954c630b68 100644
--- a/pkg/tsdb/models.go
+++ b/pkg/tsdb/models.go
@@ -46,8 +46,8 @@ type QueryResult struct {
}
type TimeSeries struct {
- Name string
- Points [][2]float64
+ Name string `json:"name"`
+ Points [][2]float64 `json:"points"`
}
type TimeSeriesSlice []*TimeSeries
diff --git a/public/app/core/components/jsontree/jsontree.ts b/public/app/core/components/jsontree/jsontree.ts
new file mode 100644
index 00000000000..14f6012ce12
--- /dev/null
+++ b/public/app/core/components/jsontree/jsontree.ts
@@ -0,0 +1,200 @@
+
+/** Created by: Alex Wendland (me@alexwendland.com), 2014-08-06
+ *
+ * angular-json-tree
+ *
+ * Directive for creating a tree-view out of a JS Object. Only loads
+ * sub-nodes on demand in order to improve performance of rendering large
+ * objects.
+ *
+ * Attributes:
+ * - object (Object, 2-way): JS object to build the tree from
+ * - start-expanded (Boolean, 1-way, ?=true): should the tree default to expanded
+ *
+ * Usage:
+ * // In the controller
+ * scope.someObject = {
+ * test: 'hello',
+ * array: [1,1,2,3,5,8]
+ * };
+ * // In the html
+ *
+ *
+ * Dependencies:
+ * - utils (json-tree.js)
+ * - ajsRecursiveDirectiveHelper (json-tree.js)
+ *
+ * Test: json-tree-test.js
+ */
+
+import angular from 'angular';
+import coreModule from 'app/core/core_module';
+
+var utils = {
+ /* See link for possible type values to check against.
+ * http://stackoverflow.com/questions/4622952/json-object-containing-array
+ *
+ * Value Class Type
+ * -------------------------------------
+ * "foo" String string
+ * new String("foo") String object
+ * 1.2 Number number
+ * new Number(1.2) Number object
+ * true Boolean boolean
+ * new Boolean(true) Boolean object
+ * new Date() Date object
+ * new Error() Error object
+ * [1,2,3] Array object
+ * new Array(1, 2, 3) Array object
+ * new Function("") Function function
+ * /abc/g RegExp object (function in Nitro/V8)
+ * new RegExp("meow") RegExp object (function in Nitro/V8)
+ * {} Object object
+ * new Object() Object object
+ */
+ is: function is(obj, clazz) {
+ return Object.prototype.toString.call(obj).slice(8, -1) === clazz;
+ },
+
+ // See above for possible values
+ whatClass: function whatClass(obj) {
+ return Object.prototype.toString.call(obj).slice(8, -1);
+ },
+
+ // Iterate over an objects keyset
+ forKeys: function forKeys(obj, f) {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key) && typeof obj[key] !== 'function') {
+ if (f(key, obj[key])) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+coreModule.directive('jsonTree', [function jsonTreeDirective() {
+ return {
+ restrict: 'E',
+ scope: {
+ object: '=',
+ startExpanded: '@',
+ rootName: '@',
+ },
+ template: ''
+ };
+}]);
+
+coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNodeDirective(ajsRecursiveDirectiveHelper) {
+ return {
+ restrict: 'E',
+ scope: {
+ key: '=',
+ value: '=',
+ startExpanded: '@'
+ },
+ compile: function jsonNodeDirectiveCompile(elem) {
+ return ajsRecursiveDirectiveHelper.compile(elem, this);
+ },
+ template: ' {{key}}' +
+ ' {{value}}' +
+ ' ' +
+ ' {{preview}}' +
+ ' ' +
+ ' - ' +
+ ' ' +
+ '
' +
+ '
',
+ pre: function jsonNodeDirectiveLink(scope, elem, attrs) {
+ // Set value's type as Class for CSS styling
+ elem.addClass(utils.whatClass(scope.value).toLowerCase());
+ // If the value is an Array or Object, use expandable view type
+ if (utils.is(scope.value, 'Object') || utils.is(scope.value, 'Array')) {
+ scope.isExpandable = true;
+ // Add expandable class for CSS usage
+ elem.addClass('expandable');
+ // Setup preview text
+ var isArray = utils.is(scope.value, 'Array');
+ scope.preview = isArray ? '[ ' : '{ ';
+ utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) {
+ if (isArray) {
+ scope.preview += value + ', ';
+ } else {
+ scope.preview += key + ': ' + value + ', ';
+ }
+ });
+ scope.preview = scope.preview.substring(0, scope.preview.length - (scope.preview.length > 2 ? 2 : 0)) + (isArray ? ' ]' : ' }');
+ // If directive initially has isExpanded set, also set shouldRender to true
+ if (scope.startExpanded) {
+ scope.shouldRender = true;
+ elem.addClass('expanded');
+ }
+ // Setup isExpanded state handling
+ scope.isExpanded = scope.startExpanded;
+ scope.toggleExpanded = function jsonNodeDirectiveToggleExpanded() {
+ scope.isExpanded = !scope.isExpanded;
+ if (scope.isExpanded) {
+ elem.addClass('expanded');
+ } else {
+ elem.removeClass('expanded');
+ }
+ // For delaying subnode render until requested
+ scope.shouldRender = true;
+ };
+ } else {
+ scope.isExpandable = false;
+ // Add expandable class for CSS usage
+ elem.addClass('not-expandable');
+ }
+ }
+ };
+}]);
+
+/** Added by: Alex Wendland (me@alexwendland.com), 2014-08-09
+ * Source: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives
+ *
+ * Used to allow for recursion within directives
+ */
+coreModule.factory('ajsRecursiveDirectiveHelper', ['$compile', function RecursiveDirectiveHelper($compile) {
+ return {
+ /**
+ * Manually compiles the element, fixing the recursion loop.
+ * @param element
+ * @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
+ * @returns An object containing the linking functions.
+ */
+ compile: function RecursiveDirectiveHelperCompile(element, link) {
+ // Normalize the link parameter
+ if (angular.isFunction(link)) {
+ link = {
+ post: link
+ };
+ }
+
+ // Break the recursion loop by removing the contents
+ var contents = element.contents().remove();
+ var compiledContents;
+ return {
+ pre: (link && link.pre) ? link.pre : null,
+ /**
+ * Compiles and re-adds the contents
+ */
+ post: function RecursiveDirectiveHelperCompilePost(scope, element) {
+ // Compile the contents
+ if (!compiledContents) {
+ compiledContents = $compile(contents);
+ }
+ // Re-add the compiled contents to the element
+ compiledContents(scope, function (clone) {
+ element.append(clone);
+ });
+
+ // Call the post-linking function, if any
+ if (link && link.post) {
+ link.post.apply(null, arguments);
+ }
+ }
+ };
+ }
+ };
+}]);
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 4dae139b66d..1174e267f4e 100644
--- a/public/app/core/core.ts
+++ b/public/app/core/core.ts
@@ -19,6 +19,7 @@ import "./directives/rebuild_on_change";
import "./directives/give_focus";
import './jquery_extended';
import './partials';
+import './components/jsontree/jsontree';
import {grafanaAppDirective} from './components/grafana_app';
import {sideMenuDirective} from './components/sidemenu/sidemenu';
diff --git a/public/app/plugins/panel/graph/alert_tab_ctrl.ts b/public/app/plugins/panel/graph/alert_tab_ctrl.ts
index 95b5cd267f8..559e9c6b78e 100644
--- a/public/app/plugins/panel/graph/alert_tab_ctrl.ts
+++ b/public/app/plugins/panel/graph/alert_tab_ctrl.ts
@@ -150,7 +150,7 @@ export class AlertTabCtrl {
};
return this.backendSrv.post('/api/alerts/test', payload).then(res => {
- this.testResult = angular.toJson(res, true);
+ this.testResult = res;
this.testing = false;
});
}
diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html
index f42b0021872..c5b85f5de59 100644
--- a/public/app/plugins/panel/graph/partials/tab_alerting.html
+++ b/public/app/plugins/panel/graph/partials/tab_alerting.html
@@ -132,9 +132,7 @@
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss
index 194d7a5487c..77a88efcf7a 100644
--- a/public/sass/_grafana.scss
+++ b/public/sass/_grafana.scss
@@ -71,6 +71,7 @@
@import "components/query_editor";
@import "components/tabbed_view";
@import "components/query_part";
+@import "components/jsontree";
// PAGES
@import "pages/login";
diff --git a/public/sass/components/_jsontree.scss b/public/sass/components/_jsontree.scss
new file mode 100644
index 00000000000..668382180f7
--- /dev/null
+++ b/public/sass/components/_jsontree.scss
@@ -0,0 +1,61 @@
+/* Structure */
+json-tree {
+ .json-tree-key {
+ vertical-align: middle;
+ }
+ .expandable {
+ position: relative;
+ &::before {
+ pointer-events: none;
+ }
+ &::before, & > .key {
+ cursor: pointer;
+ }
+ }
+ .json-tree-branch-preview {
+ display: inline-block;
+ vertical-align: middle;
+ }
+}
+
+/* Looks */
+json-tree {
+ ul {
+ padding-left: $spacer;
+ }
+ li, ul {
+ list-style: none;
+ }
+ li {
+ line-height: 1.3rem;
+ }
+ .json-tree-key {
+ color: $variable;
+ padding: 5px 10px 5px 15px;
+ &::after {
+ content: ':';
+ }
+ }
+ json-node.expandable {
+ &::before {
+ content: '\25b6';
+ position: absolute;
+ left: 0px;
+ font-size: 10px;
+ transition: transform .1s ease;
+ }
+ &.expanded::before {
+ transform: rotate(90deg);
+ }
+ }
+ .json-tree-leaf-value, .json-tree-branch-preview {
+ word-break: break-all;
+ }
+ .json-tree-branch-preview {
+ overflow: hidden;
+ font-style: italic;
+ max-width: 40%;
+ height: 1.5em;
+ opacity: .7;
+ }
+}