diff --git a/package.json b/package.json
index 47102820d58..19e91a31ffe 100644
--- a/package.json
+++ b/package.json
@@ -68,6 +68,7 @@
"grunt-jscs": "3.0.1",
"grunt-sass-lint": "^0.2.2",
"grunt-sync": "^0.6.2",
+ "json-formatter-js": "^2.2.0",
"karma-sinon": "^1.0.5",
"lodash": "^4.17.2",
"mousetrap": "^1.6.0",
diff --git a/public/app/core/components/jsonview/helpers.ts b/public/app/core/components/jsonview/helpers.ts
new file mode 100644
index 00000000000..3ab36a19a7d
--- /dev/null
+++ b/public/app/core/components/jsonview/helpers.ts
@@ -0,0 +1,110 @@
+// #<{(|
+// * Escapes `"` charachters from string
+// |)}>#
+// function escapeString(str: string): string {
+// return str.replace('"', '\"');
+// }
+//
+// #<{(|
+// * Determines if a value is an object
+// |)}>#
+// export function isObject(value: any): boolean {
+// var type = typeof value;
+// return !!value && (type === 'object');
+// }
+//
+// #<{(|
+// * Gets constructor name of an object.
+// * From http://stackoverflow.com/a/332429
+// *
+// |)}>#
+// export function getObjectName(object: Object): string {
+// if (object === undefined) {
+// return '';
+// }
+// if (object === null) {
+// return 'Object';
+// }
+// if (typeof object === 'object' && !object.constructor) {
+// return 'Object';
+// }
+//
+// const funcNameRegex = /function ([^(]*)/;
+// const results = (funcNameRegex).exec((object).constructor.toString());
+// if (results && results.length > 1) {
+// return results[1];
+// } else {
+// return '';
+// }
+// }
+//
+// #<{(|
+// * Gets type of an object. Returns "null" for null objects
+// |)}>#
+// export function getType(object: Object): string {
+// if (object === null) { return 'null'; }
+// return typeof object;
+// }
+//
+// #<{(|
+// * Generates inline preview for a JavaScript object based on a value
+// |)}>#
+// export function getValuePreview (object: Object, value: string): string {
+// var type = getType(object);
+//
+// if (type === 'null' || type === 'undefined') { return type; }
+//
+// if (type === 'string') {
+// value = '"' + escapeString(value) + '"';
+// }
+// if (type === 'function'){
+//
+// // Remove content of the function
+// return object.toString()
+// .replace(/[\r\n]/g, '')
+// .replace(/\{.*\}/, '') + '{…}';
+// }
+// return value;
+// }
+//
+// #<{(|
+// * Generates inline preview for a JavaScript object
+// |)}>#
+// export function getPreview(object: string): string {
+// let value = '';
+// if (isObject(object)) {
+// value = getObjectName(object);
+// if (Array.isArray(object)) {
+// value += '[' + object.length + ']';
+// }
+// } else {
+// value = getValuePreview(object, object);
+// }
+// return value;
+// }
+//
+// #<{(|
+// * Generates a prefixed CSS class name
+// |)}>#
+// export function cssClass(className: string): string {
+// return `json-formatter-${className}`;
+// }
+//
+// #<{(|
+// * Creates a new DOM element wiht given type and class
+// * TODO: move me to helpers
+// |)}>#
+// export function createElement(type: string, className?: string, content?: Element|string): Element {
+// const el = document.createElement(type);
+// if (className) {
+// el.classList.add(cssClass(className));
+// }
+// if (content !== undefined) {
+// if (content instanceof Node) {
+// el.appendChild(content);
+// } else {
+// el.appendChild(document.createTextNode(String(content)));
+// }
+// }
+// return el;
+// }
diff --git a/public/app/core/components/jsonview/jsonview.ts b/public/app/core/components/jsonview/jsonview.ts
new file mode 100644
index 00000000000..71994ceef59
--- /dev/null
+++ b/public/app/core/components/jsonview/jsonview.ts
@@ -0,0 +1,453 @@
+// import {
+// isObject,
+// getObjectName,
+// getType,
+// getValuePreview,
+// getPreview,
+// cssClass,
+// createElement
+// } from './helpers';
+//
+// import './style.less';
+//
+// const DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/;
+// const PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/;
+// const JSON_DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
+//
+// // When toggleing, don't animated removal or addition of more than a few items
+// const MAX_ANIMATED_TOGGLE_ITEMS = 10;
+//
+// const requestAnimationFrame = window.requestAnimationFrame || function(cb: ()=>void) { cb(); return 0; };
+//
+// export interface JSONFormatterConfiguration {
+// hoverPreviewEnabled?: boolean;
+// hoverPreviewArrayCount?: number;
+// hoverPreviewFieldCount?: number;
+// animateOpen?: boolean;
+// animateClose?: boolean;
+// theme?: string;
+// };
+//
+// const _defaultConfig: JSONFormatterConfiguration = {
+// hoverPreviewEnabled: false,
+// hoverPreviewArrayCount: 100,
+// hoverPreviewFieldCount: 5,
+// animateOpen: true,
+// animateClose: true,
+// theme: null
+// };
+//
+//
+// #<{(|*
+// * @class JSONFormatter
+// *
+// * JSONFormatter allows you to render JSON objects in HTML with a
+// * **collapsible** navigation.
+// |)}>#
+// export default class JSONFormatter {
+//
+// // Hold the open state after the toggler is used
+// private _isOpen: boolean = null;
+//
+// // A reference to the element that we render to
+// private element: Element;
+//
+// #<{(|*
+// * @param {object} json The JSON object you want to render. It has to be an
+// * object or array. Do NOT pass raw JSON string.
+// *
+// * @param {number} [open=1] his number indicates up to how many levels the
+// * rendered tree should expand. Set it to `0` to make the whole tree collapsed
+// * or set it to `Infinity` to expand the tree deeply
+// *
+// * @param {object} [config=defaultConfig] -
+// * defaultConfig = {
+// * hoverPreviewEnabled: false,
+// * hoverPreviewArrayCount: 100,
+// * hoverPreviewFieldCount: 5
+// * }
+// *
+// * Available configurations:
+// * #####Hover Preview
+// * * `hoverPreviewEnabled`: enable preview on hover
+// * * `hoverPreviewArrayCount`: number of array items to show in preview Any
+// * array larger than this number will be shown as `Array[XXX]` where `XXX`
+// * is length of the array.
+// * * `hoverPreviewFieldCount`: number of object properties to show for object
+// * preview. Any object with more properties that thin number will be
+// * truncated.
+// *
+// * @param {string} [key=undefined] The key that this object in it's parent
+// * context
+// |)}>#
+// constructor(public json: any, private open = 1, private config: JSONFormatterConfiguration = _defaultConfig, private key?: string) {
+//
+// // Setting default values for config object
+// if (this.config.hoverPreviewEnabled === undefined) {
+// this.config.hoverPreviewEnabled = _defaultConfig.hoverPreviewEnabled;
+// }
+// if (this.config.hoverPreviewArrayCount === undefined) {
+// this.config.hoverPreviewArrayCount = _defaultConfig.hoverPreviewArrayCount;
+// }
+// if (this.config.hoverPreviewFieldCount === undefined) {
+// this.config.hoverPreviewFieldCount = _defaultConfig.hoverPreviewFieldCount;
+// }
+// }
+//
+// #<{(|
+// * is formatter open?
+// |)}>#
+// private get isOpen(): boolean {
+// if (this._isOpen !== null) {
+// return this._isOpen;
+// } else {
+// return this.open > 0;
+// }
+// }
+//
+// #<{(|
+// * set open state (from toggler)
+// |)}>#
+// private set isOpen(value: boolean) {
+// this._isOpen = value;
+// }
+//
+// #<{(|
+// * is this a date string?
+// |)}>#
+// private get isDate(): boolean {
+// return (this.type === 'string') &&
+// (DATE_STRING_REGEX.test(this.json) ||
+// JSON_DATE_REGEX.test(this.json) ||
+// PARTIAL_DATE_REGEX.test(this.json));
+// }
+//
+// #<{(|
+// * is this a URL string?
+// |)}>#
+// private get isUrl(): boolean {
+// return this.type === 'string' && (this.json.indexOf('http') === 0);
+// }
+//
+// #<{(|
+// * is this an array?
+// |)}>#
+// private get isArray(): boolean {
+// return Array.isArray(this.json);
+// }
+//
+// #<{(|
+// * is this an object?
+// * Note: In this context arrays are object as well
+// |)}>#
+// private get isObject(): boolean {
+// return isObject(this.json);
+// }
+//
+// #<{(|
+// * is this an empty object with no properties?
+// |)}>#
+// private get isEmptyObject(): boolean {
+// return !this.keys.length && !this.isArray;
+// }
+//
+// #<{(|
+// * is this an empty object or array?
+// |)}>#
+// private get isEmpty(): boolean {
+// return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray);
+// }
+//
+// #<{(|
+// * did we recieve a key argument?
+// * This means that the formatter was called as a sub formatter of a parent formatter
+// |)}>#
+// private get hasKey(): boolean {
+// return typeof this.key !== 'undefined';
+// }
+//
+// #<{(|
+// * if this is an object, get constructor function name
+// |)}>#
+// private get constructorName(): string {
+// return getObjectName(this.json);
+// }
+//
+// #<{(|
+// * get type of this value
+// * Possible values: all JavaScript primitive types plus "array" and "null"
+// |)}>#
+// private get type(): string {
+// return getType(this.json);
+// }
+//
+// #<{(|
+// * get object keys
+// * If there is an empty key we pad it wit quotes to make it visible
+// |)}>#
+// private get keys(): string[] {
+// if (this.isObject) {
+// return Object.keys(this.json).map((key)=> key ? key : '""');
+// } else {
+// return [];
+// }
+// }
+//
+// #<{(|*
+// * Toggles `isOpen` state
+// *
+// |)}>#
+// toggleOpen() {
+// this.isOpen = !this.isOpen;
+//
+// if (this.element) {
+// if (this.isOpen) {
+// this.appendChildren(this.config.animateOpen);
+// } else{
+// this.removeChildren(this.config.animateClose);
+// }
+// this.element.classList.toggle(cssClass('open'));
+// }
+// }
+//
+// #<{(|*
+// * Open all children up to a certain depth.
+// * Allows actions such as expand all/collapse all
+// *
+// |)}>#
+// openAtDepth(depth = 1) {
+// if (depth < 0) {
+// return;
+// }
+//
+// this.open = depth;
+// this.isOpen = (depth !== 0);
+//
+// if (this.element) {
+// this.removeChildren(false);
+//
+// if (depth === 0) {
+// this.element.classList.remove(cssClass('open'));
+// } else {
+// this.appendChildren(this.config.animateOpen);
+// this.element.classList.add(cssClass('open'));
+// }
+// }
+// }
+//
+// #<{(|*
+// * Generates inline preview
+// *
+// * @returns {string}
+// |)}>#
+// getInlinepreview() {
+// if (this.isArray) {
+//
+// // if array length is greater then 100 it shows "Array[101]"
+// if (this.json.length > this.config.hoverPreviewArrayCount) {
+// return `Array[${this.json.length}]`;
+// } else {
+// return `[${this.json.map(getPreview).join(', ')}]`;
+// }
+// } else {
+//
+// const keys = this.keys;
+//
+// // the first five keys (like Chrome Developer Tool)
+// const narrowKeys = keys.slice(0, this.config.hoverPreviewFieldCount);
+//
+// // json value schematic information
+// const kvs = narrowKeys.map(key => `${key}:${getPreview(this.json[key])}`);
+//
+// // if keys count greater then 5 then show ellipsis
+// const ellipsis = keys.length >= this.config.hoverPreviewFieldCount ? '…' : '';
+//
+// return `{${kvs.join(', ')}${ellipsis}}`;
+// }
+// }
+//
+//
+// #<{(|*
+// * Renders an HTML element and installs event listeners
+// *
+// * @returns {HTMLDivElement}
+// |)}>#
+// render(): HTMLDivElement {
+//
+// // construct the root element and assign it to this.element
+// this.element = createElement('div', 'row');
+//
+// // construct the toggler link
+// const togglerLink = createElement('a', 'toggler-link');
+//
+// // if this is an object we need a wrapper span (toggler)
+// if (this.isObject) {
+// togglerLink.appendChild(createElement('span', 'toggler'));
+// }
+//
+// // if this is child of a parent formatter we need to append the key
+// if (this.hasKey) {
+// togglerLink.appendChild(createElement('span', 'key', `${this.key}:`));
+// }
+//
+// // Value for objects and arrays
+// if (this.isObject) {
+//
+// // construct the value holder element
+// const value = createElement('span', 'value');
+//
+// // we need a wrapper span for objects
+// const objectWrapperSpan = createElement('span');
+//
+// // get constructor name and append it to wrapper span
+// var constructorName = createElement('span', 'constructor-name', this.constructorName);
+// objectWrapperSpan.appendChild(constructorName);
+//
+// // if it's an array append the array specific elements like brackets and length
+// if (this.isArray) {
+// const arrayWrapperSpan = createElement('span');
+// arrayWrapperSpan.appendChild(createElement('span', 'bracket', '['));
+// arrayWrapperSpan.appendChild(createElement('span', 'number', (this.json.length)));
+// arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']'));
+// objectWrapperSpan.appendChild(arrayWrapperSpan);
+// }
+//
+// // append object wrapper span to toggler link
+// value.appendChild(objectWrapperSpan);
+// togglerLink.appendChild(value);
+//
+// // Primitive values
+// } else {
+//
+// // make a value holder element
+// const value = this.isUrl ? createElement('a') : createElement('span');
+//
+// // add type and other type related CSS classes
+// value.classList.add(cssClass(this.type));
+// if (this.isDate) {
+// value.classList.add(cssClass('date'));
+// }
+// if (this.isUrl) {
+// value.classList.add(cssClass('url'));
+// value.setAttribute('href', this.json);
+// }
+//
+// // Append value content to value element
+// const valuePreview = getValuePreview(this.json, this.json);
+// value.appendChild(document.createTextNode(valuePreview));
+//
+// // append the value element to toggler link
+// togglerLink.appendChild(value);
+// }
+//
+// // if hover preview is enabled, append the inline preview element
+// if (this.isObject && this.config.hoverPreviewEnabled) {
+// const preview = createElement('span', 'preview-text');
+// preview.appendChild(document.createTextNode(this.getInlinepreview()));
+// togglerLink.appendChild(preview);
+// }
+//
+// // construct a children element
+// const children = createElement('div', 'children');
+//
+// // set CSS classes for children
+// if (this.isObject) {
+// children.classList.add(cssClass('object'));
+// }
+// if (this.isArray) {
+// children.classList.add(cssClass('array'));
+// }
+// if (this.isEmpty) {
+// children.classList.add(cssClass('empty'));
+// }
+//
+// // set CSS classes for root element
+// if (this.config && this.config.theme) {
+// this.element.classList.add(cssClass(this.config.theme));
+// }
+// if (this.isOpen) {
+// this.element.classList.add(cssClass('open'));
+// }
+//
+// // append toggler and children elements to root element
+// this.element.appendChild(togglerLink);
+// this.element.appendChild(children);
+//
+// // if formatter is set to be open call appendChildren
+// if (this.isObject && this.isOpen) {
+// this.appendChildren();
+// }
+//
+// // add event listener for toggling
+// if (this.isObject) {
+// togglerLink.addEventListener('click', this.toggleOpen.bind(this));
+// }
+//
+// return this.element as HTMLDivElement;
+// }
+//
+// #<{(|*
+// * Appends all the children to children element
+// * Animated option is used when user triggers this via a click
+// |)}>#
+// appendChildren(animated = false) {
+// const children = this.element.querySelector(`div.${cssClass('children')}`);
+//
+// if (!children || this.isEmpty) { return; }
+//
+// if (animated) {
+// let index = 0;
+// const addAChild = ()=> {
+// const key = this.keys[index];
+// const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
+// children.appendChild(formatter.render());
+//
+// index += 1;
+//
+// if (index < this.keys.length) {
+// if (index > MAX_ANIMATED_TOGGLE_ITEMS) {
+// addAChild();
+// } else {
+// requestAnimationFrame(addAChild);
+// }
+// }
+// };
+//
+// requestAnimationFrame(addAChild);
+//
+// } else {
+// this.keys.forEach(key => {
+// const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
+// children.appendChild(formatter.render());
+// });
+// }
+// }
+//
+// #<{(|*
+// * Removes all the children from children element
+// * Animated option is used when user triggers this via a click
+// |)}>#
+// removeChildren(animated = false) {
+// const childrenElement = this.element.querySelector(`div.${cssClass('children')}`) as HTMLDivElement;
+//
+// if (animated) {
+// let childrenRemoved = 0;
+// const removeAChild = ()=> {
+// if (childrenElement && childrenElement.children.length) {
+// childrenElement.removeChild(childrenElement.children[0]);
+// childrenRemoved += 1;
+// if (childrenRemoved > MAX_ANIMATED_TOGGLE_ITEMS) {
+// removeAChild();
+// } else {
+// requestAnimationFrame(removeAChild);
+// }
+// }
+// };
+// requestAnimationFrame(removeAChild);
+// } else {
+// if (childrenElement) {
+// childrenElement.innerHTML = '';
+// }
+// }
+// }
+// }
diff --git a/public/app/core/components/response_viewer.ts b/public/app/core/components/response_viewer.ts
new file mode 100644
index 00000000000..5861e010fb3
--- /dev/null
+++ b/public/app/core/components/response_viewer.ts
@@ -0,0 +1,63 @@
+///
{{ctrl.lastError}}
- {{ctrl.lastResponse}}
-