소스 검색

Merge pull request #14135 from grafana/panel-edit-in-react

React Panel Editor Mode (For Angular and React Panels)
Torkel Ödegaard 7 년 전
부모
커밋
9404d36ab5
100개의 변경된 파일2727개의 추가작업 그리고 506개의 파일을 삭제
  1. 1 0
      pkg/plugins/datasource_plugin.go
  2. 67 0
      public/app/core/components/CopyToClipboard/CopyToClipboard.tsx
  3. 43 0
      public/app/core/components/Form/Element.tsx
  4. 53 0
      public/app/core/components/Form/Input.test.tsx
  5. 89 0
      public/app/core/components/Form/Input.tsx
  6. 19 0
      public/app/core/components/Form/Label.tsx
  7. 11 0
      public/app/core/components/Form/__snapshots__/Input.test.tsx.snap
  8. 3 0
      public/app/core/components/Form/index.ts
  9. 51 0
      public/app/core/components/JSONFormatter/JSONFormatter.tsx
  10. 1 1
      public/app/core/components/manage_dashboards/manage_dashboards.html
  11. 4 4
      public/app/core/components/search/search_results.html
  12. 1 0
      public/app/core/constants.ts
  13. 4 0
      public/app/core/services/AngularLoader.ts
  14. 2 3
      public/app/core/table_model.ts
  15. 9 0
      public/app/core/utils/rangeutil.ts
  16. 16 0
      public/app/core/utils/validate.ts
  17. 3 0
      public/app/features/alerting/AlertTabCtrl.ts
  18. 72 0
      public/app/features/dashboard/dashgrid/AlertTab.tsx
  19. 1 2
      public/app/features/dashboard/dashgrid/DashboardGrid.tsx
  20. 45 44
      public/app/features/dashboard/dashgrid/DashboardPanel.tsx
  21. 31 0
      public/app/features/dashboard/dashgrid/DataSourceOption.tsx
  22. 22 13
      public/app/features/dashboard/dashgrid/DataSourcePicker.tsx
  23. 46 12
      public/app/features/dashboard/dashgrid/EditorTabBody.tsx
  24. 52 0
      public/app/features/dashboard/dashgrid/GeneralTab.tsx
  25. 63 43
      public/app/features/dashboard/dashgrid/PanelEditor.tsx
  26. 215 13
      public/app/features/dashboard/dashgrid/QueriesTab.tsx
  27. 226 0
      public/app/features/dashboard/dashgrid/QueryInspector.tsx
  28. 111 0
      public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx
  29. 86 2
      public/app/features/dashboard/dashgrid/VisualizationTab.tsx
  30. 0 4
      public/app/features/dashboard/dashgrid/VizTypePicker.tsx
  31. 54 3
      public/app/features/dashboard/panel_model.ts
  32. 1 1
      public/app/features/dashboard/utils/getPanelMenu.ts
  33. 21 0
      public/app/features/explore/Error.tsx
  34. 43 24
      public/app/features/explore/Explore.tsx
  35. 77 0
      public/app/features/explore/QueryEditor.tsx
  36. 35 13
      public/app/features/explore/QueryRows.tsx
  37. 8 2
      public/app/features/explore/TimePicker.tsx
  38. 22 0
      public/app/features/panel/GeneralTabCtrl.ts
  39. 0 7
      public/app/features/panel/metrics_panel_ctrl.ts
  40. 1 1
      public/app/features/panel/metrics_tab.ts
  41. 0 1
      public/app/features/panel/panel_ctrl.ts
  42. 14 46
      public/app/features/panel/panel_directive.ts
  43. 0 1
      public/app/features/panel/panel_editor_tab.ts
  44. 0 0
      public/app/features/panel/partials/general_tab.html
  45. 0 70
      public/app/features/panel/partials/metrics_tab.html
  46. 31 46
      public/app/features/panel/partials/query_editor_row.html
  47. 2 0
      public/app/features/panel/query_editor_row.ts
  48. 2 9
      public/app/features/plugins/datasource_srv.ts
  49. 0 26
      public/app/features/plugins/specs/datasource_srv.test.ts
  50. 7 5
      public/app/plugins/datasource/graphite/plugin.json
  51. 2 1
      public/app/plugins/datasource/logging/plugin.json
  52. 1 1
      public/app/plugins/datasource/mssql/plugin.json
  53. 1 1
      public/app/plugins/datasource/mysql/plugin.json
  54. 1 0
      public/app/plugins/datasource/opentsdb/plugin.json
  55. 1 1
      public/app/plugins/datasource/postgres/plugin.json
  56. 2 1
      public/app/plugins/datasource/prometheus/plugin.json
  57. 1 0
      public/app/plugins/datasource/stackdriver/plugin.json
  58. 7 0
      public/app/plugins/datasource/testdata/datasource.ts
  59. 7 12
      public/app/plugins/panel/graph/module.ts
  60. 8 0
      public/app/types/form.ts
  61. 3 1
      public/app/types/index.ts
  62. 10 0
      public/app/types/plugins.ts
  63. 33 0
      public/img/icons_dark_theme/icon_advanced.svg
  64. 69 0
      public/img/icons_dark_theme/icon_advanced_active.svg
  65. 26 0
      public/img/icons_dark_theme/icon_alerting.svg
  66. 43 0
      public/img/icons_dark_theme/icon_alerting_active.svg
  67. 25 0
      public/img/icons_dark_theme/icon_query.svg
  68. 46 0
      public/img/icons_dark_theme/icon_query_active.svg
  69. 20 0
      public/img/icons_dark_theme/icon_visualize.svg
  70. 38 0
      public/img/icons_dark_theme/icon_visualize_active.svg
  71. 33 0
      public/img/icons_light_theme/icon_advanced.svg
  72. 70 0
      public/img/icons_light_theme/icon_advanced_active.svg
  73. 26 0
      public/img/icons_light_theme/icon_alerting.svg
  74. 43 0
      public/img/icons_light_theme/icon_alerting_active.svg
  75. 25 0
      public/img/icons_light_theme/icon_query.svg
  76. 46 0
      public/img/icons_light_theme/icon_query_active.svg
  77. 22 0
      public/img/icons_light_theme/icon_visualize.svg
  78. 38 0
      public/img/icons_light_theme/icon_visualize_active.svg
  79. 9 0
      public/img/panel-tabs/alert-selected.svg
  80. 9 0
      public/img/panel-tabs/alert.svg
  81. 31 0
      public/img/panel-tabs/general-selected.svg
  82. 8 0
      public/img/panel-tabs/general.svg
  83. 31 0
      public/img/panel-tabs/queries-selected.svg
  84. 8 0
      public/img/panel-tabs/queries.svg
  85. 34 0
      public/img/panel-tabs/visualization-selected.svg
  86. 7 0
      public/img/panel-tabs/visualization.svg
  87. 20 4
      public/sass/_variables.dark.scss
  88. 21 4
      public/sass/_variables.light.scss
  89. 34 0
      public/sass/base/_icons.scss
  90. 6 0
      public/sass/components/_buttons.scss
  91. 16 13
      public/sass/components/_dashboard_grid.scss
  92. 1 0
      public/sass/components/_dashboard_list.scss
  93. 1 13
      public/sass/components/_gf-form.scss
  94. 4 0
      public/sass/components/_navbar.scss
  95. 144 28
      public/sass/components/_panel_editor.scss
  96. 7 18
      public/sass/components/_switch.scss
  97. 10 12
      public/sass/components/_toolbar.scss
  98. 6 0
      public/sass/layout/_page.scss
  99. 1 0
      public/sass/pages/_dashboard.scss
  100. 7 0
      public/sass/utils/_utils.scss

+ 1 - 0
pkg/plugins/datasource_plugin.go

@@ -24,6 +24,7 @@ type DataSourcePlugin struct {
 	Metrics      bool              `json:"metrics"`
 	Alerting     bool              `json:"alerting"`
 	Explore      bool              `json:"explore"`
+	Table        bool              `json:"tables"`
 	Logs         bool              `json:"logs"`
 	QueryOptions map[string]bool   `json:"queryOptions,omitempty"`
 	BuiltIn      bool              `json:"builtIn,omitempty"`

+ 67 - 0
public/app/core/components/CopyToClipboard/CopyToClipboard.tsx

@@ -0,0 +1,67 @@
+import React, { PureComponent, ReactNode } from 'react';
+import ClipboardJS from 'clipboard';
+
+interface Props {
+  text: () => string;
+  elType?: string;
+  onSuccess?: (evt: any) => void;
+  onError?: (evt: any) => void;
+  className?: string;
+  children?: ReactNode;
+}
+
+export class CopyToClipboard extends PureComponent<Props> {
+  clipboardjs: any;
+  myRef: any;
+
+  constructor(props) {
+    super(props);
+    this.myRef = React.createRef();
+  }
+
+  componentDidMount() {
+    const { text, onSuccess, onError } = this.props;
+
+    this.clipboardjs = new ClipboardJS(this.myRef.current, {
+      text: text,
+    });
+
+    if (onSuccess) {
+      this.clipboardjs.on('success', evt => {
+        evt.clearSelection();
+        onSuccess(evt);
+      });
+    }
+
+    if (onError) {
+      this.clipboardjs.on('error', evt => {
+        console.error('Action:', evt.action);
+        console.error('Trigger:', evt.trigger);
+        onError(evt);
+      });
+    }
+  }
+
+  componentWillUnmount() {
+    if (this.clipboardjs) {
+      this.clipboardjs.destroy();
+    }
+  }
+
+  getElementType = () => {
+    return this.props.elType || 'button';
+  };
+
+  render() {
+    const { elType, text, children, onError, onSuccess, ...restProps } = this.props;
+
+    return React.createElement(
+      this.getElementType(),
+      {
+        ref: this.myRef,
+        ...restProps,
+      },
+      this.props.children
+    );
+  }
+}

+ 43 - 0
public/app/core/components/Form/Element.tsx

@@ -0,0 +1,43 @@
+import React, { PureComponent, ReactNode, ReactElement } from 'react';
+import { Label } from './Label';
+import { uniqueId } from 'lodash';
+
+interface Props {
+  label?: ReactNode;
+  labelClassName?: string;
+  id?: string;
+  children: ReactElement<any>;
+}
+
+export class Element extends PureComponent<Props> {
+  elementId: string = this.props.id || uniqueId('form-element-');
+
+  get elementLabel() {
+    const { label, labelClassName } = this.props;
+
+    if (label) {
+      return (
+        <Label htmlFor={this.elementId} className={labelClassName}>
+          {label}
+        </Label>
+      );
+    }
+
+    return null;
+  }
+
+  get children() {
+    const { children } = this.props;
+
+    return React.cloneElement(children, { id: this.elementId });
+  }
+
+  render() {
+    return (
+      <div className="our-custom-wrapper-class">
+        {this.elementLabel}
+        {this.children}
+      </div>
+    );
+  }
+}

+ 53 - 0
public/app/core/components/Form/Input.test.tsx

@@ -0,0 +1,53 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { shallow } from 'enzyme';
+import { Input, EventsWithValidation } from './Input';
+import { ValidationEvents } from 'app/types';
+
+const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars';
+const testBlurValidation: ValidationEvents = {
+  [EventsWithValidation.onBlur]: [
+    {
+      rule: (value: string) => {
+        if (!value || value.length < 3) {
+          return true;
+        }
+        return false;
+      },
+      errorMessage: TEST_ERROR_MESSAGE,
+    },
+  ],
+};
+
+describe('Input', () => {
+  it('renders correctly', () => {
+    const tree = renderer.create(<Input />).toJSON();
+    expect(tree).toMatchSnapshot();
+  });
+
+  it('should validate with error onBlur', () => {
+    const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
+    const evt = {
+      persist: jest.fn,
+      target: {
+        value: 'I can not be more than 2 chars',
+      },
+    };
+
+    wrapper.find('input').simulate('blur', evt);
+    expect(wrapper.state('error')).toBe(TEST_ERROR_MESSAGE);
+  });
+
+  it('should validate without error onBlur', () => {
+    const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
+    const evt = {
+      persist: jest.fn,
+      target: {
+        value: 'Hi',
+      },
+    };
+
+    wrapper.find('input').simulate('blur', evt);
+    expect(wrapper.state('error')).toBe(null);
+  });
+});

+ 89 - 0
public/app/core/components/Form/Input.tsx

@@ -0,0 +1,89 @@
+import React, { PureComponent } from 'react';
+import { ValidationEvents, ValidationRule } from 'app/types';
+import { validate, hasValidationEvent } from 'app/core/utils/validate';
+
+export enum InputStatus {
+  Invalid = 'invalid',
+  Valid = 'valid',
+}
+
+export enum InputTypes {
+  Text = 'text',
+  Number = 'number',
+  Password = 'password',
+  Email = 'email',
+}
+
+export enum EventsWithValidation {
+  onBlur = 'onBlur',
+  onFocus = 'onFocus',
+  onChange = 'onChange',
+}
+
+interface Props extends React.HTMLProps<HTMLInputElement> {
+  validationEvents?: ValidationEvents;
+  hideErrorMessage?: boolean;
+
+  // Override event props and append status as argument
+  onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
+  onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
+  onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
+}
+
+export class Input extends PureComponent<Props> {
+  state = {
+    error: null,
+  };
+
+  get status() {
+    return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
+  }
+
+  get isInvalid() {
+    return this.status === InputStatus.Invalid;
+  }
+
+  validatorAsync = (validationRules: ValidationRule[]) => {
+    return evt => {
+      const errors = validate(evt.target.value, validationRules);
+      this.setState(prevState => {
+        return {
+          ...prevState,
+          error: errors ? errors[0] : null,
+        };
+      });
+    };
+  };
+
+  populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
+    const inputElementProps = { ...restProps };
+    Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => {
+      if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) {
+        inputElementProps[eventName] = async evt => {
+          evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
+          if (hasValidationEvent(eventName, validationEvents)) {
+            await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
+          }
+          if (restProps[eventName]) {
+            restProps[eventName].apply(null, [evt, this.status]);
+          }
+        };
+      }
+    });
+    return inputElementProps;
+  };
+
+  render() {
+    const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
+    const { error } = this.state;
+    const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : '');
+    const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
+
+    return (
+      <div className="our-custom-wrapper-class">
+        <input {...inputElementProps} className={inputClassName} />
+        {error && !hideErrorMessage && <span>{error}</span>}
+      </div>
+    );
+  }
+}

+ 19 - 0
public/app/core/components/Form/Label.tsx

@@ -0,0 +1,19 @@
+import React, { PureComponent, ReactNode } from 'react';
+
+interface Props {
+  children: ReactNode;
+  htmlFor?: string;
+  className?: string;
+}
+
+export class Label extends PureComponent<Props> {
+  render() {
+    const { children, htmlFor, className } = this.props;
+
+    return (
+      <label className={`custom-label-class ${className || ''}`} htmlFor={htmlFor}>
+        {children}
+      </label>
+    );
+  }
+}

+ 11 - 0
public/app/core/components/Form/__snapshots__/Input.test.tsx.snap

@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Input renders correctly 1`] = `
+<div
+  className="our-custom-wrapper-class"
+>
+  <input
+    className="gf-form-input"
+  />
+</div>
+`;

+ 3 - 0
public/app/core/components/Form/index.ts

@@ -0,0 +1,3 @@
+export { Element } from './Element';
+export { Input } from './Input';
+export { Label } from './Label';

+ 51 - 0
public/app/core/components/JSONFormatter/JSONFormatter.tsx

@@ -0,0 +1,51 @@
+import React, { PureComponent, createRef } from 'react';
+// import JSONFormatterJS, { JSONFormatterConfiguration } from 'json-formatter-js';
+import { JsonExplorer } from 'app/core/core'; // We have made some monkey-patching of json-formatter-js so we can't switch right now
+
+interface Props {
+  className?: string;
+  json: {};
+  config?: any;
+  open?: number;
+  onDidRender?: (formattedJson: any) => void;
+}
+
+export class JSONFormatter extends PureComponent<Props> {
+  private wrapperRef = createRef<HTMLDivElement>();
+
+  static defaultProps = {
+    open: 3,
+    config: {
+      animateOpen: true,
+    },
+  };
+
+  componentDidMount() {
+    this.renderJson();
+  }
+
+  componentDidUpdate() {
+    this.renderJson();
+  }
+
+  renderJson = () => {
+    const { json, config, open, onDidRender } = this.props;
+    const wrapperEl = this.wrapperRef.current;
+    const formatter = new JsonExplorer(json, open, config);
+    const hasChildren: boolean = wrapperEl.hasChildNodes();
+    if (hasChildren) {
+      wrapperEl.replaceChild(formatter.render(), wrapperEl.lastChild);
+    } else {
+      wrapperEl.appendChild(formatter.render());
+    }
+
+    if (onDidRender) {
+      onDidRender(formatter.json);
+    }
+  };
+
+  render() {
+    const { className } = this.props;
+    return <div className={className} ref={this.wrapperRef} />;
+  }
+}

+ 1 - 1
public/app/core/components/manage_dashboards/manage_dashboards.html

@@ -67,7 +67,7 @@
       <gf-form-checkbox
         on-change="ctrl.onSelectAllChanged()"
         checked="ctrl.selectAllChecked"
-        switch-class="gf-form-switch--transparent"
+        switch-class="gf-form-checkbox--transparent"
       />
       <div class="search-results-filter-row__filters">
         <div class="gf-form-select-wrapper" ng-show="!(ctrl.canMove || ctrl.canDelete)">

+ 4 - 4
public/app/core/components/search/search_results.html

@@ -1,11 +1,11 @@
 <div ng-repeat="section in ctrl.results" class="search-section">
   <div class="search-section__header pointer" ng-hide="section.hideHeader" ng-class="{'selected': section.selected}" ng-click="ctrl.toggleFolderExpand(section)">
-    <div ng-click="ctrl.toggleSelection(section, $event)">
+    <div ng-click="ctrl.toggleSelection(section, $event)" class="center-vh">
       <gf-form-checkbox
          ng-show="ctrl.editable"
          on-change="ctrl.selectionChanged($event)"
          checked="section.checked"
-         switch-class="gf-form-switch--transparent gf-form-switch--search-result__section">
+         switch-class="gf-form-checkbox--transparent">
       </gf-form-checkbox>
     </div>
     <i class="search-section__header__icon" ng-class="section.icon"></i>
@@ -21,12 +21,12 @@
 
   <div ng-if="section.expanded">
     <a ng-repeat="item in section.items" class="search-item search-item--indent" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}" >
-      <div ng-click="ctrl.toggleSelection(item, $event)">
+      <div ng-click="ctrl.toggleSelection(item, $event)" class="center-vh">
         <gf-form-checkbox
            ng-show="ctrl.editable"
            on-change="ctrl.selectionChanged()"
            checked="item.checked"
-           switch-class="gf-form-switch--transparent gf-form-switch--search-result__item">
+           switch-class="gf-form-checkbox--transparent">
         </gf-form-checkbox>
       </div>
       <span class="search-item__icon">

+ 1 - 0
public/app/core/constants.ts

@@ -14,3 +14,4 @@ export const DASHBOARD_TOP_PADDING = 20;
 
 export const PANEL_HEADER_HEIGHT = 27;
 export const PANEL_BORDER = 2;
+export const PANEL_OPTIONS_KEY_PREFIX = 'options-';

+ 4 - 0
public/app/core/services/AngularLoader.ts

@@ -5,6 +5,7 @@ import _ from 'lodash';
 export interface AngularComponent {
   destroy();
   digest();
+  getScope();
 }
 
 export class AngularLoader {
@@ -28,6 +29,9 @@ export class AngularLoader {
       digest: () => {
         scope.$digest();
       },
+      getScope: () => {
+        return scope;
+      },
     };
   }
 }

+ 2 - 3
public/app/core/table_model.ts

@@ -86,11 +86,10 @@ export function mergeTablesIntoModel(dst?: TableModel, ...tables: TableModel[]):
   if (arguments.length === 1) {
     return model;
   }
-
   // Single query returns data columns and rows as is
   if (arguments.length === 2) {
-    model.columns = [...tables[0].columns];
-    model.rows = [...tables[0].rows];
+    model.columns = tables[0].hasOwnProperty('columns') ? [...tables[0].columns] : [];
+    model.rows = tables[0].hasOwnProperty('rows') ? [...tables[0].rows] : [];
     return model;
   }
 

+ 9 - 0
public/app/core/utils/rangeutil.ts

@@ -159,3 +159,12 @@ export function describeTimeRange(range: RawTimeRange): string {
 
   return range.from.toString() + ' to ' + range.to.toString();
 }
+
+export const isValidTimeSpan = (value: string) => {
+  if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) {
+    return true;
+  }
+
+  const info = describeTextRange(value);
+  return info.invalid !== true;
+};

+ 16 - 0
public/app/core/utils/validate.ts

@@ -0,0 +1,16 @@
+import { ValidationRule, ValidationEvents } from 'app/types';
+import { EventsWithValidation } from 'app/core/components/Form/Input';
+
+export const validate = (value: string, validationRules: ValidationRule[]) => {
+  const errors = validationRules.reduce((acc, currRule) => {
+    if (!currRule.rule(value)) {
+      return acc.concat(currRule.errorMessage);
+    }
+    return acc;
+  }, []);
+  return errors.length > 0 ? errors : null;
+};
+
+export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => {
+  return validationEvents && validationEvents[event];
+};

+ 3 - 0
public/app/features/alerting/AlertTabCtrl.ts

@@ -1,4 +1,5 @@
 import _ from 'lodash';
+import coreModule from 'app/core/core_module';
 import { ThresholdMapper } from './state/ThresholdMapper';
 import { QueryPart } from 'app/core/components/query_part/query_part';
 import alertDef from './state/alertDef';
@@ -430,3 +431,5 @@ export function alertTab() {
     controller: AlertTabCtrl,
   };
 }
+
+coreModule.directive('alertTab', alertTab);

+ 72 - 0
public/app/features/dashboard/dashgrid/AlertTab.tsx

@@ -0,0 +1,72 @@
+import React, { PureComponent } from 'react';
+
+import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+import { EditorTabBody } from './EditorTabBody';
+import 'app/features/alerting/AlertTabCtrl';
+
+interface Props {
+  angularPanel?: AngularComponent;
+}
+
+export class AlertTab extends PureComponent<Props> {
+  element: any;
+  component: AngularComponent;
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    if (this.shouldLoadAlertTab()) {
+      this.loadAlertTab();
+    }
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (this.shouldLoadAlertTab()) {
+      this.loadAlertTab();
+    }
+  }
+
+  shouldLoadAlertTab() {
+    return this.props.angularPanel && this.element;
+  }
+
+  componentWillUnmount() {
+    if (this.component) {
+      this.component.destroy();
+    }
+  }
+
+  loadAlertTab() {
+    const { angularPanel } = this.props;
+
+    const scope = angularPanel.getScope();
+
+    // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet
+    if (!scope.$$childHead) {
+      setTimeout(() => {
+        this.forceUpdate();
+      });
+      return;
+    }
+
+    const panelCtrl = scope.$$childHead.ctrl;
+    const loader = getAngularLoader();
+    const template = '<alert-tab />';
+
+    const scopeProps = {
+      ctrl: panelCtrl,
+    };
+
+    this.component = loader.load(this.element, scopeProps, template);
+  }
+
+  render() {
+    return (
+      <EditorTabBody heading="Alert" toolbarItems={[]}>
+        <div ref={element => (this.element = element)} />
+      </EditorTabBody>
+    );
+  }
+}

+ 1 - 2
public/app/features/dashboard/dashgrid/DashboardGrid.tsx

@@ -176,10 +176,9 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
 
   renderPanels() {
     const panelElements = [];
-    console.log('render panels');
 
     for (const panel of this.props.dashboard.panels) {
-      const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen });
+      const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen });
       panelElements.push(
         <div key={panel.id.toString()} className={panelClasses} id={`panel-${panel.id}`}>
           <DashboardPanel

+ 45 - 44
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import config from 'app/core/config';
+import classNames from 'classnames';
 
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
 import { importPluginModule } from 'app/features/plugins/plugin_loader';
@@ -23,11 +24,11 @@ export interface Props {
 
 export interface State {
   plugin: PanelPlugin;
+  angularPanel: AngularComponent;
 }
 
 export class DashboardPanel extends PureComponent<Props, State> {
-  element: any;
-  angularPanel: AngularComponent;
+  element: HTMLElement;
   specialPanels = {};
 
   constructor(props) {
@@ -35,6 +36,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
 
     this.state = {
       plugin: null,
+      angularPanel: null,
     };
 
     this.specialPanels['row'] = this.renderRow.bind(this);
@@ -54,11 +56,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
   }
 
   onPluginTypeChanged = (plugin: PanelPlugin) => {
-    this.props.panel.changeType(plugin.id);
-    this.loadPlugin();
-  };
-
-  onAngularPluginTypeChanged = () => {
+    this.props.panel.changeType(plugin.id, this.state.angularPanel !== null);
     this.loadPlugin();
   };
 
@@ -93,28 +91,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
   }
 
   componentDidUpdate() {
-    this.loadPlugin();
-
-    // handle angular plugin loading
-    if (!this.element || this.angularPanel) {
+    if (!this.element || this.state.angularPanel) {
       return;
     }
 
     const loader = getAngularLoader();
     const template = '<plugin-component type="panel" class="panel-height-helper"></plugin-component>';
     const scopeProps = { panel: this.props.panel, dashboard: this.props.dashboard };
-    this.angularPanel = loader.load(this.element, scopeProps, template);
+    const angularPanel = loader.load(this.element, scopeProps, template);
+
+    this.setState({ angularPanel });
   }
 
-  cleanUpAngularPanel() {
-    if (this.angularPanel) {
-      this.angularPanel.destroy();
-      this.angularPanel = null;
+  cleanUpAngularPanel(unmounted?: boolean) {
+    if (this.state.angularPanel) {
+      this.state.angularPanel.destroy();
+
+      if (!unmounted) {
+        this.setState({ angularPanel: null });
+      }
     }
   }
 
   componentWillUnmount() {
-    this.cleanUpAngularPanel();
+    this.cleanUpAngularPanel(true);
   }
 
   onMouseEnter = () => {
@@ -129,25 +129,16 @@ export class DashboardPanel extends PureComponent<Props, State> {
     const { dashboard, panel } = this.props;
     const { plugin } = this.state;
 
-    const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
-    const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
-    // this might look strange with these classes that change when edit, but
-    // I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel
-    return (
-      <div className={containerClass}>
-        <div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
-          <PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />
-        </div>
-        {panel.isEditing && (
-          <PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
-        )}
-      </div>
-    );
+    return <PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />;
+  }
+
+  renderAngularPanel() {
+    return <div ref={element => (this.element = element)} className="panel-height-helper" />;
   }
 
   render() {
-    const { panel } = this.props;
-    const { plugin } = this.state;
+    const { panel, dashboard, isFullscreen, isEditing } = this.props;
+    const { plugin, angularPanel } = this.state;
 
     if (this.isSpecial()) {
       return this.specialPanels[panel.type]();
@@ -158,19 +149,29 @@ export class DashboardPanel extends PureComponent<Props, State> {
       return null;
     }
 
-    // if exporting PanelComponent it must be a react panel
-    if (plugin.exports.Panel) {
-      return this.renderReactPanel();
-    }
+    const containerClass = classNames({ 'panel-editor-container': isEditing, 'panel-height-helper': !isEditing });
+    const panelWrapperClass = classNames({
+      'panel-wrapper': true,
+      'panel-wrapper--edit': isEditing,
+      'panel-wrapper--view': isFullscreen && !isEditing,
+    });
 
-    // legacy angular rendering
     return (
-      <div
-        ref={element => (this.element = element)}
-        className="panel-height-helper"
-        onMouseEnter={this.onMouseEnter}
-        onMouseLeave={this.onMouseLeave}
-      />
+      <div className={containerClass}>
+        <div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
+          {plugin.exports.Panel && this.renderReactPanel()}
+          {plugin.exports.PanelCtrl && this.renderAngularPanel()}
+        </div>
+        {panel.isEditing && (
+          <PanelEditor
+            panel={panel}
+            plugin={plugin}
+            dashboard={dashboard}
+            angularPanel={angularPanel}
+            onTypeChanged={this.onPluginTypeChanged}
+          />
+        )}
+      </div>
     );
   }
 }

+ 31 - 0
public/app/features/dashboard/dashgrid/DataSourceOption.tsx

@@ -0,0 +1,31 @@
+import React, { SFC } from 'react';
+import Tooltip from 'app/core/components/Tooltip/Tooltip';
+
+interface Props {
+  label: string;
+  placeholder?: string;
+  name?: string;
+  value?: string;
+  onChange?: (evt: any) => void;
+  tooltipInfo?: any;
+}
+
+export const DataSourceOptions: SFC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
+  const dsOption = (
+    <div className="gf-form gf-form--flex-end">
+      <label className="gf-form-label">{label}</label>
+      <input
+        type="text"
+        className="gf-form-input width-6"
+        placeholder={placeholder}
+        name={name}
+        spellCheck={false}
+        onBlur={evt => onChange(evt.target.value)}
+      />
+    </div>
+  );
+
+  return tooltipInfo ? <Tooltip content={tooltipInfo}>{dsOption}</Tooltip> : dsOption;
+};
+
+export default DataSourceOptions;

+ 22 - 13
public/app/features/dashboard/dashgrid/DataSourcePicker.tsx

@@ -2,13 +2,14 @@ import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import _ from 'lodash';
 
-import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
 import { DataSourceSelectItem } from 'app/types';
 
-interface Props {}
+interface Props {
+  onChangeDataSource: (ds: any) => void;
+  datasources: DataSourceSelectItem[];
+}
 
 interface State {
-  datasources: DataSourceSelectItem[];
   searchQuery: string;
 }
 
@@ -17,31 +18,32 @@ export class DataSourcePicker extends PureComponent<Props, State> {
 
   constructor(props) {
     super(props);
-
     this.state = {
-      datasources: getDatasourceSrv().getMetricSources(),
       searchQuery: '',
     };
   }
 
   getDataSources() {
-    const { datasources, searchQuery } = this.state;
+    const { searchQuery } = this.state;
     const regex = new RegExp(searchQuery, 'i');
+    const { datasources } = this.props;
 
     const filtered = datasources.filter(item => {
       return regex.test(item.name) || regex.test(item.meta.name);
     });
 
-    return _.sortBy(filtered, 'sort');
+    return filtered;
   }
 
-  renderDataSource = (ds: DataSourceSelectItem, index) => {
+  renderDataSource = (ds: DataSourceSelectItem, index: number) => {
+    const { onChangeDataSource } = this.props;
+    const onClick = () => onChangeDataSource(ds);
     const cssClass = classNames({
       'ds-picker-list__item': true,
     });
 
     return (
-      <div key={index} className={cssClass} title={ds.name}>
+      <div key={index} className={cssClass} title={ds.name} onClick={onClick}>
         <img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
         <div className="ds-picker-list__name">{ds.name}</div>
       </div>
@@ -54,7 +56,16 @@ export class DataSourcePicker extends PureComponent<Props, State> {
     }, 300);
   }
 
+  onSearchQueryChange = evt => {
+    const value = evt.target.value;
+    this.setState(prevState => ({
+      ...prevState,
+      searchQuery: value,
+    }));
+  };
+
   renderFilters() {
+    const { searchQuery } = this.state;
     return (
       <>
         <label className="gf-form--has-input-icon">
@@ -63,13 +74,11 @@ export class DataSourcePicker extends PureComponent<Props, State> {
             className="gf-form-input width-13"
             placeholder=""
             ref={elem => (this.searchInput = elem)}
+            onChange={this.onSearchQueryChange}
+            value={searchQuery}
           />
           <i className="gf-form-input-icon fa fa-search" />
         </label>
-        <div className="p-l-1">
-          <button className="btn toggle-btn gf-form-btn active">All</button>
-          <button className="btn toggle-btn gf-form-btn">Favorites</button>
-        </div>
       </>
     );
   }

+ 46 - 12
public/app/features/dashboard/dashgrid/EditorTabBody.tsx

@@ -4,7 +4,8 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn';
 
 interface Props {
   children: JSX.Element;
-  main: EditorToolBarView;
+  heading: string;
+  main?: EditorToolBarView;
   toolbarItems: EditorToolBarView[];
 }
 
@@ -12,11 +13,14 @@ export interface EditorToolBarView {
   title: string;
   imgSrc?: string;
   icon?: string;
-  render: () => JSX.Element;
+  disabled?: boolean;
+  onClick?: () => void;
+  render: (closeFunction: any) => JSX.Element | JSX.Element[];
 }
 
 interface State {
   openView?: EditorToolBarView;
+  fadeIn: boolean;
 }
 
 export class EditorTabBody extends PureComponent<Props, State> {
@@ -25,9 +29,14 @@ export class EditorTabBody extends PureComponent<Props, State> {
 
     this.state = {
       openView: null,
+      fadeIn: false,
     };
   }
 
+  componentDidMount() {
+    this.setState({ fadeIn: true });
+  }
+
   onToggleToolBarView = (item: EditorToolBarView) => {
     this.setState({
       openView: item === this.state.openView ? null : item,
@@ -38,9 +47,24 @@ export class EditorTabBody extends PureComponent<Props, State> {
     this.setState({ openView: null });
   };
 
+  static getDerivedStateFromProps(props, state) {
+    if (state.openView) {
+      const activeToolbarItem = props.toolbarItems.find(
+        item => item.title === state.openView.title && item.icon === state.openView.icon
+      );
+      if (activeToolbarItem) {
+        return {
+          ...state,
+          openView: activeToolbarItem,
+        };
+      }
+    }
+    return state;
+  }
+
   renderMainSelection(view: EditorToolBarView) {
     return (
-      <div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title}>
+      <div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title + view.icon}>
         <img className="toolbar__main-image" src={view.imgSrc} />
         <div className="toolbar__main-name">{view.title}</div>
         <i className="fa fa-caret-down" />
@@ -49,9 +73,16 @@ export class EditorTabBody extends PureComponent<Props, State> {
   }
 
   renderButton(view: EditorToolBarView) {
+    const onClick = () => {
+      if (view.onClick) {
+        view.onClick();
+      }
+      this.onToggleToolBarView(view);
+    };
+
     return (
-      <div className="nav-buttons" key={view.title}>
-        <button className="btn navbar-button" onClick={() => this.onToggleToolBarView(view)}>
+      <div className="nav-buttons" key={view.title + view.icon}>
+        <button className="btn navbar-button" onClick={onClick} disabled={view.disabled}>
           {view.icon && <i className={view.icon} />} {view.title}
         </button>
       </div>
@@ -64,29 +95,32 @@ export class EditorTabBody extends PureComponent<Props, State> {
         <button className="toolbar-subview__close" onClick={this.onCloseOpenView}>
           <i className="fa fa-chevron-up" />
         </button>
-        {view.render()}
+        {view.render(this.onCloseOpenView)}
       </div>
     );
   }
 
   render() {
-    const { children, toolbarItems, main } = this.props;
-    const { openView } = this.state;
+    const { children, toolbarItems, main, heading } = this.props;
+    const { openView, fadeIn } = this.state;
 
     return (
       <>
         <div className="toolbar">
-          {this.renderMainSelection(main)}
+          <div className="toolbar__heading">{heading}</div>
+          {main && this.renderMainSelection(main)}
           <div className="gf-form--grow" />
           {toolbarItems.map(item => this.renderButton(item))}
         </div>
         <div className="panel-editor__scroll">
           <CustomScrollbar autoHide={false}>
+            <FadeIn in={openView !== null} duration={200}>
+              <div className="panel-editor__toolbar-view">{openView && this.renderOpenView(openView)}</div>
+            </FadeIn>
             <div className="panel-editor__content">
-              <FadeIn in={openView !== null} duration={200}>
-                {openView && this.renderOpenView(openView)}
+              <FadeIn in={fadeIn} duration={50}>
+                {children}
               </FadeIn>
-              {children}
             </div>
           </CustomScrollbar>
         </div>

+ 52 - 0
public/app/features/dashboard/dashgrid/GeneralTab.tsx

@@ -0,0 +1,52 @@
+import React, { PureComponent } from 'react';
+
+import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+import { EditorTabBody } from './EditorTabBody';
+
+import { PanelModel } from '../panel_model';
+import './../../panel/GeneralTabCtrl';
+
+interface Props {
+  panel: PanelModel;
+}
+
+export class GeneralTab extends PureComponent<Props> {
+  element: any;
+  component: AngularComponent;
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    if (!this.element) {
+      return;
+    }
+
+    const { panel } = this.props;
+
+    const loader = getAngularLoader();
+    const template = '<panel-general-tab />';
+    const scopeProps = {
+      ctrl: {
+        panel: panel,
+      },
+    };
+
+    this.component = loader.load(this.element, scopeProps, template);
+  }
+
+  componentWillUnmount() {
+    if (this.component) {
+      this.component.destroy();
+    }
+  }
+
+  render() {
+    return (
+      <EditorTabBody heading="Basic Panel Options" toolbarItems={[]}>
+        <div ref={element => (this.element = element)} />
+      </EditorTabBody>
+    );
+  }
+}

+ 63 - 43
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -3,38 +3,36 @@ import classNames from 'classnames';
 
 import { QueriesTab } from './QueriesTab';
 import { VisualizationTab } from './VisualizationTab';
+import { GeneralTab } from './GeneralTab';
+import { AlertTab } from './AlertTab';
 
+import config from 'app/core/config';
 import { store } from 'app/store/store';
 import { updateLocation } from 'app/core/actions';
+import { AngularComponent } from 'app/core/services/AngularLoader';
 
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
 
+import Tooltip from 'app/core/components/Tooltip/Tooltip';
+
 interface PanelEditorProps {
   panel: PanelModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
+  angularPanel?: AngularComponent;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 
 interface PanelEditorTab {
   id: string;
   text: string;
-  icon: string;
 }
 
 export class PanelEditor extends PureComponent<PanelEditorProps> {
-  tabs: PanelEditorTab[];
-
   constructor(props) {
     super(props);
-
-    this.tabs = [
-      { id: 'queries', text: 'Queries', icon: 'fa fa-database' },
-      { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' },
-      { id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' },
-    ];
   }
 
   onChangeTab = (tab: PanelEditorTab) => {
@@ -47,44 +45,64 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
     this.forceUpdate();
   };
 
-  onClose = () => {
-    store.dispatch(
-      updateLocation({
-        query: { tab: null, fullscreen: null, edit: null },
-        partial: true,
-      })
-    );
-  };
+  renderCurrentTab(activeTab: string) {
+    const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props;
+
+    switch (activeTab) {
+      case 'advanced':
+        return <GeneralTab panel={panel} />;
+      case 'queries':
+        return <QueriesTab panel={panel} dashboard={dashboard} />;
+      case 'alerts':
+        return <AlertTab angularPanel={angularPanel} />;
+      case 'visualization':
+        return (
+          <VisualizationTab
+            panel={panel}
+            dashboard={dashboard}
+            plugin={plugin}
+            onTypeChanged={onTypeChanged}
+            angularPanel={angularPanel}
+          />
+        );
+      default:
+        return null;
+    }
+  }
 
   render() {
-    const { panel, dashboard, onTypeChanged, plugin } = this.props;
-    const { location } = store.getState();
-    const activeTab = location.query.tab || 'queries';
+    const { plugin } = this.props;
+    const activeTab = store.getState().location.query.tab || 'queries';
+
+    const tabs = [
+      { id: 'queries', text: 'Queries' },
+      { id: 'visualization', text: 'Visualization' },
+      { id: 'advanced', text: 'Advanced' },
+    ];
+
+    if (config.alertingEnabled && plugin.id === 'graph') {
+      tabs.push({
+        id: 'alerts',
+        text: 'Alerts',
+      });
+    }
 
     return (
       <div className="panel-editor-container__editor">
-        <div className="panel-editor-resizer">
-          <div className="panel-editor-resizer__handle">
-            <div className="panel-editor-resizer__handle-dots" />
-          </div>
-        </div>
+        {
+          // <div className="panel-editor-resizer">
+          //   <div className="panel-editor-resizer__handle">
+          //     <div className="panel-editor-resizer__handle-dots" />
+          //   </div>
+          // </div>
+        }
 
         <div className="panel-editor-tabs">
-          <ul className="gf-tabs">
-            {this.tabs.map(tab => {
-              return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
-            })}
-          </ul>
-
-          <button className="panel-editor-tabs__close" onClick={this.onClose}>
-            <i className="fa fa-reply" />
-          </button>
+          {tabs.map(tab => {
+            return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
+          })}
         </div>
-
-        {activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
-        {activeTab === 'visualization' && (
-          <VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
-        )}
+        <div className="panel-editor__right">{this.renderCurrentTab(activeTab)}</div>
       </div>
     );
   }
@@ -98,15 +116,17 @@ interface TabItemParams {
 
 function TabItem({ tab, activeTab, onClick }: TabItemParams) {
   const tabClasses = classNames({
-    'gf-tabs-link': true,
+    'panel-editor-tabs__link': true,
     active: activeTab === tab.id,
   });
 
   return (
-    <li className="gf-tabs-item" onClick={() => onClick(tab)}>
+    <div className="panel-editor-tabs__item" onClick={() => onClick(tab)}>
       <a className={tabClasses}>
-        <i className={tab.icon} /> {tab.text}
+        <Tooltip content={`${tab.text}`} className="popper__manager--block" placement="auto">
+          <i className={`gicon gicon-${tab.id}${activeTab === tab.id ? '-active' : ''}`} />
+        </Tooltip>
       </a>
-    </li>
+    </div>
   );
 }

+ 215 - 13
public/app/features/dashboard/dashgrid/QueriesTab.tsx

@@ -1,23 +1,62 @@
-import React, { PureComponent } from 'react';
-
+import React, { SFC, PureComponent } from 'react';
+import DataSourceOption from './DataSourceOption';
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
 import { EditorTabBody } from './EditorTabBody';
 import { DataSourcePicker } from './DataSourcePicker';
-
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
+import './../../panel/metrics_tab';
+import config from 'app/core/config';
+import { QueryInspector } from './QueryInspector';
+import { TimeRangeOptions } from './TimeRangeOptions';
+
+// Services
+import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
+import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
+import { DataSourceSelectItem } from 'app/types';
+
+import Remarkable from 'remarkable';
 
 interface Props {
   panel: PanelModel;
   dashboard: DashboardModel;
 }
 
-export class QueriesTab extends PureComponent<Props> {
+interface Help {
+  isLoading: boolean;
+  helpHtml: any;
+}
+
+interface State {
+  currentDatasource: DataSourceSelectItem;
+  help: Help;
+  hideTimeOverride: boolean;
+}
+
+interface LoadingPlaceholderProps {
+  text: string;
+}
+
+const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
+
+export class QueriesTab extends PureComponent<Props, State> {
   element: any;
   component: AngularComponent;
+  datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
+  backendSrv: BackendSrv = getBackendSrv();
 
   constructor(props) {
     super(props);
+    const { panel } = props;
+
+    this.state = {
+      currentDatasource: this.datasources.find(datasource => datasource.value === panel.datasource),
+      help: {
+        isLoading: false,
+        helpHtml: null,
+      },
+      hideTimeOverride: false,
+    };
   }
 
   componentDidMount() {
@@ -26,7 +65,6 @@ export class QueriesTab extends PureComponent<Props> {
     }
 
     const { panel, dashboard } = this.props;
-
     const loader = getAngularLoader();
     const template = '<metrics-tab />';
     const scopeProps = {
@@ -46,27 +84,191 @@ export class QueriesTab extends PureComponent<Props> {
     }
   }
 
+  onChangeDataSource = datasource => {
+    const { panel } = this.props;
+    const { currentDatasource } = this.state;
+    // switching to mixed
+    if (datasource.meta.mixed) {
+      panel.targets.forEach(target => {
+        target.datasource = panel.datasource;
+        if (!target.datasource) {
+          target.datasource = config.defaultDatasource;
+        }
+      });
+    } else if (currentDatasource && currentDatasource.meta.mixed) {
+      panel.targets.forEach(target => {
+        delete target.datasource;
+      });
+    }
+
+    panel.datasource = datasource.value;
+    panel.refresh();
+
+    this.setState(prevState => ({
+      ...prevState,
+      currentDatasource: datasource,
+    }));
+  };
+
+  loadHelp = () => {
+    const { currentDatasource } = this.state;
+    const hasHelp = currentDatasource.meta.hasQueryHelp;
+
+    if (hasHelp) {
+      this.setState(prevState => ({
+        ...prevState,
+        help: {
+          helpHtml: <h2>Loading help...</h2>,
+          isLoading: true,
+        },
+      }));
+
+      this.backendSrv
+        .get(`/api/plugins/${currentDatasource.meta.id}/markdown/query_help`)
+        .then(res => {
+          const md = new Remarkable();
+          const helpHtml = md.render(res); // TODO: Clean out dangerous code? Previous: this.helpHtml = this.$sce.trustAsHtml(md.render(res));
+          this.setState(prevState => ({
+            ...prevState,
+            help: {
+              helpHtml: <div className="markdown-html" dangerouslySetInnerHTML={{ __html: helpHtml }} />,
+              isLoading: false,
+            },
+          }));
+        })
+        .catch(() => {
+          this.setState(prevState => ({
+            ...prevState,
+            help: {
+              helpHtml: 'Error occured when loading help',
+              isLoading: false,
+            },
+          }));
+        });
+    }
+  };
+
+  renderOptions = close => {
+    const { currentDatasource } = this.state;
+    const { queryOptions } = currentDatasource.meta;
+    const { panel } = this.props;
+
+    const onChangeFn = (panelKey: string) => {
+      return (value: string | number) => {
+        panel[panelKey] = value;
+        panel.refresh();
+      };
+    };
+
+    const allOptions = {
+      cacheTimeout: {
+        label: 'Cache timeout',
+        placeholder: '60',
+        name: 'cacheTimeout',
+        value: panel.cacheTimeout,
+        tooltipInfo: (
+          <>
+            If your time series store has a query cache this option can override the default cache timeout. Specify a
+            numeric value in seconds.
+          </>
+        ),
+      },
+      maxDataPoints: {
+        label: 'Max data points',
+        placeholder: 'auto',
+        name: 'maxDataPoints',
+        value: panel.maxDataPoints,
+        tooltipInfo: (
+          <>
+            The maximum data points the query should return. For graphs this is automatically set to one data point per
+            pixel.
+          </>
+        ),
+      },
+      minInterval: {
+        label: 'Min time interval',
+        placeholder: '0',
+        name: 'minInterval',
+        value: panel.interval,
+        panelKey: 'interval',
+        tooltipInfo: (
+          <>
+            A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '}
+            <code>1m</code> if your data is written every minute. Access auto interval via variable{' '}
+            <code>$__interval</code> for time range string and <code>$__interval_ms</code> for numeric variable that can
+            be used in math expressions.
+          </>
+        ),
+      },
+    };
+
+    const dsOptions = queryOptions
+      ? Object.keys(queryOptions).map(key => {
+          const options = allOptions[key];
+          return <DataSourceOption key={key} {...options} onChange={onChangeFn(allOptions[key].panelKey || key)} />;
+        })
+      : null;
+
+    return (
+      <>
+        <TimeRangeOptions panel={this.props.panel} />
+        {dsOptions}
+      </>
+    );
+  };
+
+  renderQueryInspector = () => {
+    const { panel } = this.props;
+    return <QueryInspector panel={panel} LoadingPlaceholder={LoadingPlaceholder} />;
+  };
+
+  renderHelp = () => {
+    const { helpHtml, isLoading } = this.state.help;
+    return isLoading ? <LoadingPlaceholder text="Loading help..." /> : helpHtml;
+  };
+
   render() {
-    const currentDataSource = {
-      title: 'ProductionDB',
-      imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
-      render: () => <DataSourcePicker />,
+    const { currentDatasource } = this.state;
+    const { hasQueryHelp } = currentDatasource.meta;
+    const dsInformation = {
+      title: currentDatasource.name,
+      imgSrc: currentDatasource.meta.info.logos.small,
+      render: closeOpenView => (
+        <DataSourcePicker
+          datasources={this.datasources}
+          onChangeDataSource={ds => {
+            closeOpenView();
+            this.onChangeDataSource(ds);
+          }}
+        />
+      ),
     };
 
     const queryInspector = {
       title: 'Query Inspector',
-      render: () => <h2>hello</h2>,
+      render: this.renderQueryInspector,
     };
 
     const dsHelp = {
       title: '',
       icon: 'fa fa-question',
-      render: () => <h2>hello</h2>,
+      disabled: !hasQueryHelp,
+      onClick: this.loadHelp,
+      render: this.renderHelp,
+    };
+
+    const options = {
+      title: '',
+      icon: 'fa fa-cog',
+      disabled: false,
+      render: this.renderOptions,
     };
 
     return (
-      <EditorTabBody main={currentDataSource} toolbarItems={[queryInspector, dsHelp]}>
-        <div ref={element => (this.element = element)} style={{ width: '100%' }} />
+      <EditorTabBody heading="Queries" main={dsInformation} toolbarItems={[options, queryInspector, dsHelp]}>
+        <>
+          <div ref={element => (this.element = element)} style={{ width: '100%' }} />
+        </>
       </EditorTabBody>
     );
   }

+ 226 - 0
public/app/features/dashboard/dashgrid/QueryInspector.tsx

@@ -0,0 +1,226 @@
+import React, { PureComponent } from 'react';
+import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
+import appEvents from 'app/core/app_events';
+import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
+
+interface DsQuery {
+  isLoading: boolean;
+  response: {};
+}
+
+interface Props {
+  panel: any;
+  LoadingPlaceholder: any;
+}
+
+interface State {
+  allNodesExpanded: boolean;
+  isMocking: boolean;
+  mockedResponse: string;
+  dsQuery: DsQuery;
+}
+
+export class QueryInspector extends PureComponent<Props, State> {
+  formattedJson: any;
+  clipboard: any;
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      allNodesExpanded: null,
+      isMocking: false,
+      mockedResponse: '',
+      dsQuery: {
+        isLoading: false,
+        response: {},
+      },
+    };
+  }
+
+  componentDidMount() {
+    const { panel } = this.props;
+    panel.events.on('refresh', this.onPanelRefresh);
+    appEvents.on('ds-request-response', this.onDataSourceResponse);
+    panel.refresh();
+  }
+
+  componentWillUnmount() {
+    const { panel } = this.props;
+    appEvents.off('ds-request-response', this.onDataSourceResponse);
+    panel.events.off('refresh', this.onPanelRefresh);
+  }
+
+  handleMocking(response) {
+    const { mockedResponse } = this.state;
+    let mockedData;
+    try {
+      mockedData = JSON.parse(mockedResponse);
+    } catch (err) {
+      appEvents.emit('alert-error', ['R: Failed to parse mocked response']);
+      return;
+    }
+
+    response.data = mockedData;
+  }
+
+  onPanelRefresh = () => {
+    this.setState(prevState => ({
+      ...prevState,
+      dsQuery: {
+        isLoading: true,
+        response: {},
+      },
+    }));
+  };
+
+  onDataSourceResponse = (response: any = {}) => {
+    if (this.state.isMocking) {
+      this.handleMocking(response);
+      return;
+    }
+
+    response = { ...response }; // clone - dont modify the response
+
+    if (response.headers) {
+      delete response.headers;
+    }
+
+    if (response.config) {
+      response.request = response.config;
+      delete response.config;
+      delete response.request.transformRequest;
+      delete response.request.transformResponse;
+      delete response.request.paramSerializer;
+      delete response.request.jsonpCallbackParam;
+      delete response.request.headers;
+      delete response.request.requestId;
+      delete response.request.inspect;
+      delete response.request.retry;
+      delete response.request.timeout;
+    }
+
+    if (response.data) {
+      response.response = response.data;
+
+      delete response.data;
+      delete response.status;
+      delete response.statusText;
+      delete response.$$config;
+    }
+    this.setState(prevState => ({
+      ...prevState,
+      dsQuery: {
+        isLoading: false,
+        response: response,
+      },
+    }));
+  };
+
+  setFormattedJson = formattedJson => {
+    this.formattedJson = formattedJson;
+  };
+
+  getTextForClipboard = () => {
+    return JSON.stringify(this.formattedJson, null, 2);
+  };
+
+  onClipboardSuccess = () => {
+    appEvents.emit('alert-success', ['Content copied to clipboard']);
+  };
+
+  onToggleExpand = () => {
+    this.setState(prevState => ({
+      ...prevState,
+      allNodesExpanded: !this.state.allNodesExpanded,
+    }));
+  };
+
+  onToggleMocking = () => {
+    this.setState(prevState => ({
+      ...prevState,
+      isMocking: !this.state.isMocking,
+    }));
+  };
+
+  getNrOfOpenNodes = () => {
+    if (this.state.allNodesExpanded === null) {
+      return 3; // 3 is default, ie when state is null
+    } else if (this.state.allNodesExpanded) {
+      return 20;
+    }
+    return 1;
+  };
+
+  setMockedResponse = evt => {
+    const mockedResponse = evt.target.value;
+    this.setState(prevState => ({
+      ...prevState,
+      mockedResponse,
+    }));
+  };
+
+  renderExpandCollapse = () => {
+    const { allNodesExpanded } = this.state;
+
+    const collapse = (
+      <>
+        <i className="fa fa-minus-square-o" /> Collapse All
+      </>
+    );
+    const expand = (
+      <>
+        <i className="fa fa-plus-square-o" /> Expand All
+      </>
+    );
+    return allNodesExpanded ? collapse : expand;
+  };
+
+  render() {
+    const { response, isLoading } = this.state.dsQuery;
+    const { LoadingPlaceholder } = this.props;
+    const { isMocking } = this.state;
+    const openNodes = this.getNrOfOpenNodes();
+
+    if (isLoading) {
+      return <LoadingPlaceholder text="Loading query inspector..." />;
+    }
+
+    return (
+      <>
+        <div>
+          {/*
+          <button className="btn btn-transparent btn-p-x-0 m-r-1" onClick={this.onToggleMocking}>
+            Mock response
+          </button>
+          */}
+          <button className="btn btn-transparent btn-p-x-0 m-r-1" onClick={this.onToggleExpand}>
+            {this.renderExpandCollapse()}
+          </button>
+
+          <CopyToClipboard
+            className="btn btn-transparent btn-p-x-0"
+            text={this.getTextForClipboard}
+            onSuccess={this.onClipboardSuccess}
+          >
+            <i className="fa fa-clipboard" /> Copy to Clipboard
+          </CopyToClipboard>
+        </div>
+
+        {!isMocking && <JSONFormatter json={response} open={openNodes} onDidRender={this.setFormattedJson} />}
+        {isMocking && (
+          <div className="query-troubleshooter__body">
+            <div className="gf-form p-l-1 gf-form--v-stretch">
+              <textarea
+                className="gf-form-input"
+                style={{ width: '95%' }}
+                rows={10}
+                onInput={this.setMockedResponse}
+                placeholder="JSON"
+              />
+            </div>
+          </div>
+        )}
+      </>
+    );
+  }
+}

+ 111 - 0
public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx

@@ -0,0 +1,111 @@
+import React, { PureComponent } from 'react';
+import { Switch } from 'app/core/components/Switch/Switch';
+import { Input } from 'app/core/components/Form';
+import { isValidTimeSpan } from 'app/core/utils/rangeutil';
+import { ValidationEvents } from 'app/types';
+import { EventsWithValidation } from 'app/core/components/Form/Input';
+import { PanelModel } from '../panel_model';
+import { InputStatus } from 'app/core/components/Form/Input';
+
+const timeRangeValidationEvents: ValidationEvents = {
+  [EventsWithValidation.onBlur]: [
+    {
+      rule: value => {
+        if (!value) {
+          return true;
+        }
+        return isValidTimeSpan(value);
+      },
+      errorMessage: 'Not a valid timespan',
+    },
+  ],
+};
+
+const emptyToNull = (value: string) => {
+  return value === '' ? null : value;
+};
+
+interface Props {
+  panel: PanelModel;
+}
+
+export class TimeRangeOptions extends PureComponent<Props> {
+  onOverrideTime = (evt, status: InputStatus) => {
+    const { value } = evt.target;
+    const { panel } = this.props;
+    const emptyToNullValue = emptyToNull(value);
+    if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) {
+      panel.timeFrom = emptyToNullValue;
+      panel.refresh();
+    }
+  };
+
+  onTimeShift = (evt, status: InputStatus) => {
+    const { value } = evt.target;
+    const { panel } = this.props;
+    const emptyToNullValue = emptyToNull(value);
+    if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) {
+      panel.timeShift = emptyToNullValue;
+      panel.refresh();
+    }
+  };
+
+  onToggleTimeOverride = () => {
+    const { panel } = this.props;
+    panel.hideTimeOverride = !panel.hideTimeOverride;
+    panel.refresh();
+  };
+
+  render = () => {
+    const hideTimeOverride = this.props.panel.hideTimeOverride;
+    return (
+      <>
+        <h5 className="section-heading">Time Range</h5>
+
+        <div className="gf-form-group">
+          <div className="gf-form">
+            <span className="gf-form-label">
+              <i className="fa fa-clock-o" />
+            </span>
+
+            <span className="gf-form-label width-12">Override relative time</span>
+            <span className="gf-form-label width-6">Last</span>
+            <Input
+              type="text"
+              className="gf-form-input max-width-8"
+              placeholder="1h"
+              onBlur={this.onOverrideTime}
+              validationEvents={timeRangeValidationEvents}
+              hideErrorMessage={true}
+            />
+          </div>
+
+          <div className="gf-form">
+            <span className="gf-form-label">
+              <i className="fa fa-clock-o" />
+            </span>
+            <span className="gf-form-label width-12">Add time shift</span>
+            <span className="gf-form-label width-6">Amount</span>
+            <Input
+              type="text"
+              className="gf-form-input max-width-8"
+              placeholder="1h"
+              onBlur={this.onTimeShift}
+              validationEvents={timeRangeValidationEvents}
+              hideErrorMessage={true}
+            />
+          </div>
+
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <span className="gf-form-label">
+                <i className="fa fa-clock-o" />
+              </span>
+            </div>
+            <Switch label="Hide time override info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} />
+          </div>
+        </div>
+      </>
+    );
+  };
+}

+ 86 - 2
public/app/features/dashboard/dashgrid/VisualizationTab.tsx

@@ -1,8 +1,14 @@
+// Libraries
 import React, { PureComponent } from 'react';
 
+// Utils & Services
+import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+
+// Components
 import { EditorTabBody } from './EditorTabBody';
 import { VizTypePicker } from './VizTypePicker';
 
+// Types
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
@@ -11,18 +17,26 @@ interface Props {
   panel: PanelModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
+  angularPanel?: AngularComponent;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 
 export class VisualizationTab extends PureComponent<Props> {
+  element: HTMLElement;
+  angularOptions: AngularComponent;
+
   constructor(props) {
     super(props);
   }
 
   renderPanelOptions() {
-    const { plugin, panel } = this.props;
+    const { plugin, panel, angularPanel } = this.props;
     const { PanelOptions } = plugin.exports;
 
+    if (angularPanel) {
+      return <div ref={element => (this.element = element)} />;
+    }
+
     if (PanelOptions) {
       return <PanelOptions options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
     } else {
@@ -30,6 +44,70 @@ export class VisualizationTab extends PureComponent<Props> {
     }
   }
 
+  componentDidMount() {
+    if (this.shouldLoadAngularOptions()) {
+      this.loadAngularOptions();
+    }
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (this.props.plugin !== prevProps.plugin) {
+      this.cleanUpAngularOptions();
+    }
+
+    if (this.shouldLoadAngularOptions()) {
+      this.loadAngularOptions();
+    }
+  }
+
+  shouldLoadAngularOptions() {
+    return this.props.angularPanel && this.element && !this.angularOptions;
+  }
+
+  loadAngularOptions() {
+    const { angularPanel } = this.props;
+
+    const scope = angularPanel.getScope();
+
+    // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet
+    if (!scope.$$childHead) {
+      setTimeout(() => {
+        this.forceUpdate();
+      });
+      return;
+    }
+
+    const panelCtrl = scope.$$childHead.ctrl;
+
+    let template = '';
+    for (let i = 0; i < panelCtrl.editorTabs.length; i++) {
+      template += `
+      <div class="form-section" ng-cloak>
+        <div class="form-section__header">{{ctrl.editorTabs[${i}].title}}</div>
+        <div class="form-section__body">
+          <panel-editor-tab editor-tab="ctrl.editorTabs[${i}]" ctrl="ctrl"></panel-editor-tab>
+        </div>
+      </div>
+      `;
+    }
+
+    const loader = getAngularLoader();
+    const scopeProps = { ctrl: panelCtrl };
+
+    this.angularOptions = loader.load(this.element, scopeProps, template);
+  }
+
+  componentWillUnmount() {
+    this.cleanUpAngularOptions();
+  }
+
+  cleanUpAngularOptions() {
+    if (this.angularOptions) {
+      this.angularOptions.destroy();
+      this.angularOptions = null;
+    }
+  }
+
   onPanelOptionsChanged = (options: any) => {
     this.props.panel.updateOptions(options);
     this.forceUpdate();
@@ -48,8 +126,14 @@ export class VisualizationTab extends PureComponent<Props> {
       },
     };
 
+    const panelHelp = {
+      title: '',
+      icon: 'fa fa-question',
+      render: () => <h2>Help</h2>,
+    };
+
     return (
-      <EditorTabBody main={panelSelection} toolbarItems={[]}>
+      <EditorTabBody heading="Visualization" main={panelSelection} toolbarItems={[panelHelp]}>
         {this.renderPanelOptions()}
       </EditorTabBody>
     );

+ 0 - 4
public/app/features/dashboard/dashgrid/VizTypePicker.tsx

@@ -67,10 +67,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
           />
           <i className="gf-form-input-icon fa fa-search" />
         </label>
-        <div className="p-l-1">
-          <button className="btn toggle-btn gf-form-btn active">Basic Types</button>
-          <button className="btn toggle-btn gf-form-btn">Master Types</button>
-        </div>
       </>
     );
   }

+ 54 - 3
public/app/features/dashboard/panel_model.ts

@@ -1,5 +1,6 @@
 import { Emitter } from 'app/core/utils/emitter';
 import _ from 'lodash';
+import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants';
 
 export interface GridPos {
   x: number;
@@ -16,6 +17,42 @@ const notPersistedProperties: { [str: string]: boolean } = {
   hasRefreshed: true,
 };
 
+// For angular panels we need to clean up properties when changing type
+// To make sure the change happens without strange bugs happening when panels use same
+// named property with different type / value expectations
+// This is not required for react panels
+
+const mustKeepProps: { [str: string]: boolean } = {
+  id: true,
+  gridPos: true,
+  type: true,
+  title: true,
+  scopedVars: true,
+  repeat: true,
+  repeatIteration: true,
+  repeatPanelId: true,
+  repeatDirection: true,
+  repeatedByRow: true,
+  minSpan: true,
+  collapsed: true,
+  panels: true,
+  targets: true,
+  datasource: true,
+  timeFrom: true,
+  timeShift: true,
+  hideTimeOverride: true,
+  maxDataPoints: true,
+  interval: true,
+  description: true,
+  links: true,
+  fullscreen: true,
+  isEditing: true,
+  hasRefreshed: true,
+  events: true,
+  cacheTimeout: true,
+  nullPointMode: true,
+};
+
 const defaults: any = {
   gridPos: { x: 0, y: 0, h: 3, w: 6 },
   datasource: null,
@@ -57,6 +94,7 @@ export class PanelModel {
   isEditing: boolean;
   hasRefreshed: boolean;
   events: Emitter;
+  cacheTimeout?: any;
 
   constructor(model) {
     this.events = new Emitter();
@@ -82,7 +120,7 @@ export class PanelModel {
   }
 
   private getOptionsKey() {
-    return this.type + 'Options';
+    return 'options-' + this.type;
   }
 
   getSaveModel() {
@@ -146,11 +184,24 @@ export class PanelModel {
     this.events.emit('panel-initialized');
   }
 
-  changeType(pluginId: string) {
+  changeType(pluginId: string, fromAngularPanel: boolean) {
     this.type = pluginId;
 
-    delete this.thresholds;
+    // for now we need to remove alert rules when changing type
     delete this.alert;
+
+    // for angular panels only we need to remove all events and let angular panels do some cleanup
+    if (fromAngularPanel) {
+      this.destroy();
+
+      for (const key of _.keys(this)) {
+        if (mustKeepProps[key] || key.indexOf(PANEL_OPTIONS_KEY_PREFIX) === 0) {
+          continue;
+        }
+
+        delete this[key];
+      }
+    }
   }
 
   destroy() {

+ 1 - 1
public/app/features/dashboard/utils/getPanelMenu.ts

@@ -12,7 +12,7 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
       updateLocation({
         query: {
           panelId: panel.id,
-          edit: false,
+          edit: null,
           fullscreen: true,
         },
         partial: true,

+ 21 - 0
public/app/features/explore/Error.tsx

@@ -0,0 +1,21 @@
+import React, { SFC } from 'react';
+
+interface Props {
+  message: any;
+}
+
+export const Alert: SFC<Props> = props => {
+  const { message } = props;
+  return (
+    <div className="gf-form-group section">
+      <div className="alert-error alert">
+        <div className="alert-icon">
+          <i className="fa fa-exclamation-triangle" />
+        </div>
+        <div className="alert-body">
+          <div className="alert-title">{message}</div>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 43 - 24
public/app/features/explore/Explore.tsx

@@ -12,7 +12,7 @@ import {
   QueryHintGetter,
   QueryHint,
 } from 'app/types/explore';
-import { RawTimeRange, DataQuery } from 'app/types/series';
+import { TimeRange, DataQuery } from 'app/types/series';
 import store from 'app/core/store';
 import {
   DEFAULT_RANGE,
@@ -30,6 +30,8 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'
 import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
 import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
+import { Emitter } from 'app/core/utils/emitter';
+import * as dateMath from 'app/core/utils/datemath';
 
 import Panel from './Panel';
 import QueryRows from './QueryRows';
@@ -38,6 +40,7 @@ import Logs from './Logs';
 import Table from './Table';
 import ErrorBoundary from './ErrorBoundary';
 import TimePicker from './TimePicker';
+import { Alert } from './Error';
 
 interface ExploreProps {
   datasourceSrv: DatasourceSrv;
@@ -88,6 +91,7 @@ interface ExploreProps {
  */
 export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
   el: any;
+  exploreEvents: Emitter;
   /**
    * Current query expressions of the rows including their modifications, used for running queries.
    * Not kept in component state to prevent edit-render roundtrips.
@@ -132,6 +136,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
       };
     }
     this.modifiedQueries = initialQueries.slice();
+    this.exploreEvents = new Emitter();
   }
 
   async componentDidMount() {
@@ -140,7 +145,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     if (!datasourceSrv) {
       throw new Error('No datasource service passed as props.');
     }
-    const datasources = datasourceSrv.getExploreSources();
+    const datasources = datasourceSrv.getAll();
     const exploreDatasources = datasources.map(ds => ({
       value: ds.name,
       label: ds.name,
@@ -155,19 +160,20 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
       } else {
         datasource = await datasourceSrv.get();
       }
-      if (!datasource.meta.explore) {
-        datasource = await datasourceSrv.get(datasources[0].name);
-      }
       await this.setDatasource(datasource);
     } else {
       this.setState({ datasourceMissing: true });
     }
   }
 
+  componentWillUnmount() {
+    this.exploreEvents.removeAllListeners();
+  }
+
   async setDatasource(datasource: any, origin?: DataSource) {
     const supportsGraph = datasource.meta.metrics;
     const supportsLogs = datasource.meta.logs;
-    const supportsTable = datasource.meta.metrics;
+    const supportsTable = datasource.meta.tables;
     const datasourceId = datasource.meta.id;
     let datasourceError = null;
 
@@ -317,8 +323,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     }
   };
 
-  onChangeTime = (nextRange: RawTimeRange) => {
-    const range: RawTimeRange = {
+  // onChangeTime = (nextRange: RawTimeRange) => {
+  //   const range: RawTimeRange = {
+  //     ...nextRange,
+  //   };
+  //   this.setState({ range }, () => this.onSubmit());
+  // };
+  onChangeTime = (nextRange: TimeRange) => {
+    const range: TimeRange = {
       ...nextRange,
     };
     this.setState({ range }, () => this.onSubmit());
@@ -530,16 +542,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     const { datasource, range } = this.state;
     const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth);
 
-    const configuredQueries = [
-      {
-        ...queryOptions,
-        ...query,
-      },
-    ];
+    const configuredQueries = [Object.assign(query, queryOptions)];
 
     // Clone range for query request
-    const queryRange: RawTimeRange = { ...range };
-
+    // const queryRange: RawTimeRange = { ...range };
+    // const { from, to, raw } = this.timeSrv.timeRange();
     // Datasource is using `panelId + query.refId` for cancellation logic.
     // Using `format` here because it relates to the view panel that the request is for.
     const panelId = queryOptions.format;
@@ -549,7 +556,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
       intervalMs,
       panelId,
       targets: configuredQueries, // Datasources rely on DataQueries being passed under the targets key.
-      range: queryRange,
+      range: {
+        from: dateMath.parse(range.from, false),
+        to: dateMath.parse(range.to, true),
+        raw: range,
+      },
+      rangeRaw: range,
     };
   }
 
@@ -696,17 +708,19 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     }
     const { datasource } = this.state;
     const datasourceId = datasource.meta.id;
-    // Run all queries concurrently
+    // Run all queries concurrentlyso
     queries.forEach(async (query, rowIndex) => {
       const transaction = this.startQueryTransaction(query, rowIndex, resultType, queryOptions);
       try {
         const now = Date.now();
         const res = await datasource.query(transaction.options);
+        this.exploreEvents.emit('data-received', res.data || []);
         const latency = Date.now() - now;
         const results = resultGetter ? resultGetter(res.data) : res.data;
         this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
         this.setState({ graphRange: transaction.options.range });
       } catch (response) {
+        this.exploreEvents.emit('data-error', response);
         this.failQueryTransaction(transaction.id, response, datasourceId);
       }
     });
@@ -759,7 +773,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     const graphResult = _.flatten(
       queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
     );
-    const tableResult = mergeTablesIntoModel(
+
+    //Temp solution... How do detect if ds supports table format?
+    let tableResult;
+    tableResult = mergeTablesIntoModel(
       new TableModel(),
       ...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
     );
@@ -835,16 +852,16 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
             </button>
           </div>
         </div>
-
         {datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
-
         {datasourceMissing ? (
           <div className="explore-container">Please add a datasource that supports Explore (e.g., Prometheus).</div>
         ) : null}
 
-        {datasourceError ? (
-          <div className="explore-container">Error connecting to datasource. [{datasourceError}]</div>
-        ) : null}
+        {datasourceError && (
+          <div className="explore-container">
+            <Alert message={`Error connecting to datasource: ${datasourceError}`} />
+          </div>
+        )}
 
         {datasource && !datasourceError ? (
           <div className="explore-container">
@@ -858,6 +875,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
               onExecuteQuery={this.onSubmit}
               onRemoveQueryRow={this.onRemoveQueryRow}
               transactions={queryTransactions}
+              exploreEvents={this.exploreEvents}
+              range={range}
             />
             <main className="m-t-2">
               <ErrorBoundary>

+ 77 - 0
public/app/features/explore/QueryEditor.tsx

@@ -0,0 +1,77 @@
+import React, { PureComponent } from 'react';
+import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+import { Emitter } from 'app/core/utils/emitter';
+import { getIntervals } from 'app/core/utils/explore';
+import { DataQuery } from 'app/types';
+import { RawTimeRange } from 'app/types/series';
+import { getTimeSrv } from 'app/features/dashboard/time_srv';
+import 'app/features/plugins/plugin_loader';
+
+interface QueryEditorProps {
+  datasource: any;
+  error?: string | JSX.Element;
+  onExecuteQuery?: () => void;
+  onQueryChange?: (value: DataQuery, override?: boolean) => void;
+  initialQuery: DataQuery;
+  exploreEvents: Emitter;
+  range: RawTimeRange;
+}
+
+export default class QueryEditor extends PureComponent<QueryEditorProps, any> {
+  element: any;
+  component: AngularComponent;
+
+  async componentDidMount() {
+    if (!this.element) {
+      return;
+    }
+
+    const { datasource, initialQuery, exploreEvents, range } = this.props;
+    this.initTimeSrv(range);
+
+    const loader = getAngularLoader();
+    const template = '<plugin-component type="query-ctrl"> </plugin-component>';
+    const target = { datasource: datasource.name, ...initialQuery };
+    const scopeProps = {
+      target,
+      ctrl: {
+        refresh: () => {
+          this.props.onQueryChange(target, false);
+          this.props.onExecuteQuery();
+        },
+        events: exploreEvents,
+        panel: {
+          datasource,
+          targets: [target],
+        },
+        dashboard: {
+          getNextQueryLetter: x => '',
+        },
+        hideEditorRowActions: true,
+        ...getIntervals(range, datasource, null), // Possible to get resolution?
+      },
+    };
+
+    this.component = loader.load(this.element, scopeProps, template);
+  }
+
+  componentWillUnmount() {
+    if (this.component) {
+      this.component.destroy();
+    }
+  }
+
+  initTimeSrv(range) {
+    const timeSrv = getTimeSrv();
+    timeSrv.init({
+      time: range,
+      refresh: false,
+      getTimezone: () => 'utc',
+      timeRangeUpdated: () => console.log('refreshDashboard!'),
+    });
+  }
+
+  render() {
+    return <div ref={element => (this.element = element)} style={{ width: '100%' }} />;
+  }
+}

+ 35 - 13
public/app/features/explore/QueryRows.tsx

@@ -1,10 +1,13 @@
 import React, { PureComponent } from 'react';
 
 import { QueryTransaction, HistoryItem, QueryHint } from 'app/types/explore';
+import { Emitter } from 'app/core/utils/emitter';
 
-import DefaultQueryField from './QueryField';
+// import DefaultQueryField from './QueryField';
+import QueryEditor from './QueryEditor';
 import QueryTransactionStatus from './QueryTransactionStatus';
 import { DataSource, DataQuery } from 'app/types';
+import { RawTimeRange } from 'app/types/series';
 
 function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
   const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
@@ -27,6 +30,8 @@ interface QueryRowCommonProps {
   datasource: DataSource;
   history: HistoryItem[];
   transactions: QueryTransaction[];
+  exploreEvents: Emitter;
+  range: RawTimeRange;
 }
 
 type QueryRowProps = QueryRowCommonProps &
@@ -36,6 +41,11 @@ type QueryRowProps = QueryRowCommonProps &
   };
 
 class QueryRow extends PureComponent<QueryRowProps> {
+  onExecuteQuery = () => {
+    const { onExecuteQuery } = this.props;
+    onExecuteQuery();
+  };
+
   onChangeQuery = (value: DataQuery, override?: boolean) => {
     const { index, onChangeQuery } = this.props;
     if (onChangeQuery) {
@@ -76,27 +86,39 @@ class QueryRow extends PureComponent<QueryRowProps> {
   };
 
   render() {
-    const { datasource, history, initialQuery, transactions } = this.props;
+    const { datasource, history, initialQuery, transactions, exploreEvents, range } = this.props;
     const transactionWithError = transactions.find(t => t.error !== undefined);
     const hint = getFirstHintFromTransactions(transactions);
     const queryError = transactionWithError ? transactionWithError.error : null;
-    const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField;
+    const QueryField = datasource.pluginExports.ExploreQueryField;
     return (
       <div className="query-row">
         <div className="query-row-status">
           <QueryTransactionStatus transactions={transactions} />
         </div>
         <div className="query-row-field">
-          <QueryField
-            datasource={datasource}
-            error={queryError}
-            hint={hint}
-            initialQuery={initialQuery}
-            history={history}
-            onClickHintFix={this.onClickHintFix}
-            onPressEnter={this.onPressEnter}
-            onQueryChange={this.onChangeQuery}
-          />
+          {QueryField ? (
+            <QueryField
+              datasource={datasource}
+              error={queryError}
+              hint={hint}
+              initialQuery={initialQuery}
+              history={history}
+              onClickHintFix={this.onClickHintFix}
+              onPressEnter={this.onPressEnter}
+              onQueryChange={this.onChangeQuery}
+            />
+          ) : (
+            <QueryEditor
+              datasource={datasource}
+              error={queryError}
+              onQueryChange={this.onChangeQuery}
+              onExecuteQuery={this.onExecuteQuery}
+              initialQuery={initialQuery}
+              exploreEvents={exploreEvents}
+              range={range}
+            />
+          )}
         </div>
         <div className="query-row-tools">
           <button className="btn navbar-button navbar-button--tight" onClick={this.onClickClearButton}>

+ 8 - 2
public/app/features/explore/TimePicker.tsx

@@ -3,7 +3,7 @@ import moment from 'moment';
 
 import * as dateMath from 'app/core/utils/datemath';
 import * as rangeUtil from 'app/core/utils/rangeutil';
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange, TimeRange } from 'app/types/series';
 
 const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
 export const DEFAULT_RANGE = {
@@ -120,6 +120,12 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
       to: moment(nextTo),
     };
 
+    const nextTimeRange: TimeRange = {
+      raw: nextRange,
+      from,
+      to,
+    };
+
     this.setState(
       {
         rangeString: rangeUtil.describeTimeRange(nextRange),
@@ -127,7 +133,7 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
         toRaw: nextRange.to.format(DATE_FORMAT),
       },
       () => {
-        onChangeTime(nextRange);
+        onChangeTime(nextTimeRange);
       }
     );
   }

+ 22 - 0
public/app/features/panel/GeneralTabCtrl.ts

@@ -0,0 +1,22 @@
+import coreModule from 'app/core/core_module';
+
+export class GeneralTabCtrl {
+  panelCtrl: any;
+
+  /** @ngInject */
+  constructor($scope) {
+    this.panelCtrl = $scope.ctrl;
+  }
+}
+
+/** @ngInject */
+export function generalTab() {
+  'use strict';
+  return {
+    restrict: 'E',
+    templateUrl: 'public/app/features/panel/partials/general_tab.html',
+    controller: GeneralTabCtrl,
+  };
+}
+
+coreModule.directive('panelGeneralTab', generalTab);

+ 0 - 7
public/app/features/panel/metrics_panel_ctrl.ts

@@ -5,7 +5,6 @@ import config from 'app/core/config';
 
 import { PanelCtrl } from 'app/features/panel/panel_ctrl';
 import { getExploreUrl } from 'app/core/utils/explore';
-import { metricsTabDirective } from './metrics_tab';
 import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
 
 class MetricsPanelCtrl extends PanelCtrl {
@@ -42,7 +41,6 @@ class MetricsPanelCtrl extends PanelCtrl {
     this.panel.datasource = this.panel.datasource || null;
 
     this.events.on('refresh', this.onMetricsPanelRefresh.bind(this));
-    this.events.on('init-edit-mode', this.onInitMetricsPanelEditMode.bind(this));
     this.events.on('panel-teardown', this.onPanelTearDown.bind(this));
   }
 
@@ -53,11 +51,6 @@ class MetricsPanelCtrl extends PanelCtrl {
     }
   }
 
-  private onInitMetricsPanelEditMode() {
-    this.addEditorTab('Metrics', metricsTabDirective, 1, 'fa fa-database');
-    this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
-  }
-
   private onMetricsPanelRefresh() {
     // ignore fetching data if another panel is in fullscreen
     if (this.otherPanelInFullscreenMode()) {

+ 1 - 1
public/app/features/panel/metrics_tab.ts

@@ -41,7 +41,7 @@ export class MetricsTabCtrl {
     this.datasources = datasourceSrv.getMetricSources();
     this.panelDsValue = this.panelCtrl.panel.datasource;
 
-    // addded here as old query controller expects this on panelCtrl but
+    // added here as old query controller expects this on panelCtrl but
     // they are getting MetricsTabCtrl instead
     this.events = this.panel.events;
 

+ 0 - 1
public/app/features/panel/panel_ctrl.ts

@@ -91,7 +91,6 @@ export class PanelCtrl {
 
   initEditMode() {
     this.editorTabs = [];
-    this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
 
     this.editModeInitiated = true;
     this.events.emit('init-edit-mode', null);

+ 14 - 46
public/app/features/panel/panel_directive.ts

@@ -6,54 +6,22 @@ import baron from 'baron';
 const module = angular.module('grafana.directives');
 
 const panelTemplate = `
-  <div ng-class="{'panel-editor-container': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
-    <div ng-class="{'panel-editor-container__panel': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
-      <div class="panel-container">
-        <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
-          <span class="panel-info-corner">
-            <i class="fa"></i>
-            <span class="panel-info-corner-inner"></span>
-          </span>
-
-          <span class="panel-loading" ng-show="ctrl.loading">
-            <i class="fa fa-spinner fa-spin"></i>
-          </span>
-
-          <panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
-        </div>
-
-        <div class="panel-content">
-          <ng-transclude class="panel-height-helper"></ng-transclude>
-        </div>
+  <div class="panel-container">
+      <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
+        <span class="panel-info-corner">
+          <i class="fa"></i>
+          <span class="panel-info-corner-inner"></span>
+        </span>
+
+        <span class="panel-loading" ng-show="ctrl.loading">
+          <i class="fa fa-spinner fa-spin"></i>
+        </span>
+
+        <panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
       </div>
-    </div>
 
-    <div ng-if="ctrl.panel.isEditing" ng-class="{'panel-editor-container__editor': ctrl.panel.isEditing,
-                                                 'panel-height-helper': !ctrl.panel.isEditing}">
-      <div class="tabbed-view tabbed-view--new">
-        <div class="tabbed-view-header">
-          <h3 class="tabbed-view-panel-title">
-            {{ctrl.pluginName}}
-          </h3>
-
-          <ul class="gf-tabs">
-            <li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
-              <a class="gf-tabs-link" ng-click="ctrl.changeTab($index)" ng-class="{active: ctrl.editorTabIndex === $index}">
-                {{::tab.title}}
-              </a>
-            </li>
-          </ul>
-
-          <button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
-            <i class="fa fa-remove"></i>
-          </button>
-        </div>
-
-        <div class="tabbed-view-body">
-          <div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index" class="panel-height-helper">
-            <panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
-          </div>
-        </div>
+      <div class="panel-content">
+        <ng-transclude class="panel-height-helper"></ng-transclude>
       </div>
     </div>
   </div>

+ 0 - 1
public/app/features/panel/panel_editor_tab.ts

@@ -9,7 +9,6 @@ function panelEditorTab(dynamicDirectiveSrv) {
     scope: {
       ctrl: '=',
       editorTab: '=',
-      index: '=',
     },
     directive: scope => {
       const pluginId = scope.ctrl.pluginId;

+ 0 - 0
public/app/partials/panelgeneral.html → public/app/features/panel/partials/general_tab.html


+ 0 - 70
public/app/features/panel/partials/metrics_tab.html

@@ -1,73 +1,3 @@
-<div class="gf-form-group">
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <label class="gf-form-label">Data Source</label>
-      <gf-form-dropdown model="ctrl.panelDsValue" css-class="gf-size-auto"
-                        lookup-text="true"
-                        get-options="ctrl.getOptions(true)"
-                        on-change="ctrl.datasourceChanged($option)">
-      </gf-form-dropdown>
-		</div>
-
-		<div class="gf-form gf-form--grow">
-			<label class="gf-form-label gf-form-label--grow"></label>
-		</div>
-		<div class="gf-form" ng-if="ctrl.queryOptions">
-			<a class="gf-form-label" ng-click="ctrl.toggleOptions()">
-				<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.optionsOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.optionsOpen"></i>Options
-			</a>
-		</div>
-		<div class="gf-form" ng-if="ctrl.hasQueryHelp">
-			<button class="gf-form-label" ng-click="ctrl.toggleHelp()">
-				<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.helpOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.helpOpen"></i>Help
-			</button>
-		</div>
-		<div class="gf-form">
-			<button class="gf-form-label" ng-click="ctrl.toggleQueryTroubleshooter()" bs-tooltip="'Display query request & response'">
-				<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.queryTroubleshooterOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.queryTroubleshooterOpen"></i>Query Inspector
-			</button>
-		</div>
-	</div>
-
-	<div>
-		<div ng-if="ctrl.optionsOpen">
-			<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.minInterval">
-				<label class="gf-form-label">Min time interval</label>
-				<input type="text" class="gf-form-input width-6" placeholder="{{ctrl.panelCtrl.interval}}" ng-model="ctrl.panel.interval" spellcheck="false" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" />
-				<info-popover mode="right-absolute">
-					A lower limit for the auto group by time interval. Recommended to be set to write frequency,
-					for example <code>1m</code> if your data is written every minute. Access auto interval via variable <code>$__interval</code> for time range
-					string and <code>$__interval_ms</code> for numeric variable that can be used in math expressions.
-				</info-popover>
-			</div>
-			<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.cacheTimeout">
-				<label class="gf-form-label width-9">Cache timeout</label>
-				<input  type="text" class="gf-form-input width-6" placeholder="60" ng-model="ctrl.panel.cacheTimeout" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" spellcheck="false" />
-				<info-popover mode="right-absolute">
-					If your time series store has a query cache this option can override the default
-					cache timeout. Specify a numeric value in seconds.
-				</info-popover>
-			</div>
-			<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.maxDataPoints">
-				<label class="gf-form-label width-9">Max data points</label>
-				<input type="text" class="gf-form-input width-6" placeholder="auto" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" ng-model="ctrl.panel.maxDataPoints" spellcheck="false"  />
-				<info-popover mode="right-absolute">
-					The maximum data points the query should return. For graphs this
-					is automatically set to one data point per pixel.
-				</info-popover>
-			</div>
-		</div>
-
-		<div class="grafana-info-box" ng-if="ctrl.helpOpen">
-			<div class="markdown-html" ng-bind-html="ctrl.helpHtml"></div>
-			<a class="grafana-info-box__close" ng-click="ctrl.toggleHelp()">
-				<i class="fa fa-chevron-up"></i>
-			</a>
-		</div>
-
-		<query-troubleshooter panel-ctrl="ctrl.panelCtrl" is-open="ctrl.queryTroubleshooterOpen"></query-troubleshooter>
-	</div>
-</div>
 
 <div class="query-editor-rows gf-form-group" ng-if="ctrl.datasourceInstance">
 	<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">

+ 31 - 46
public/app/features/panel/partials/query_editor_row.html

@@ -1,59 +1,44 @@
-
 <div class="gf-form-query">
-	<div class="gf-form gf-form-query-letter-cell">
+  <div ng-if="!ctrl.hideEditorRowActions" class="gf-form gf-form-query-letter-cell">
     <label class="gf-form-label">
       <a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
-        <span  ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
+        <span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
           <i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
           <i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
         </span>
-        <span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
-        <em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
+        <span class="gf-form-query-letter-cell-letter">{{ ctrl.target.refId }}</span>
+        <em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ ctrl.target.datasource }})</em>
       </a>
-		</label>
+    </label>
   </div>
 
-	<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
-		<div class="gf-form">
-			<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
-				{{ctrl.collapsedText}}
-			</label>
-		</div>
-	</div>
+  <div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
+    <div class="gf-form">
+      <label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
+        {{ ctrl.collapsedText }}
+      </label>
+    </div>
+  </div>
 
-	<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
-	</div>
+  <div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed"></div>
 
-	<div class="gf-form">
-		<label class="gf-form-label dropdown">
-			<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
-				<i class="fa fa-bars"></i>
-			</a>
-			<ul class="dropdown-menu pull-right" role="menu">
-				<li role="menuitem" ng-if="ctrl.hasTextEditMode">
-					<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
-				</li>
-				<li role="menuitem">
-					<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
-				</li>
-				<li role="menuitem">
-					<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
-				</li>
-				<li role="menuitem">
-					<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
-				</li>
-			</ul>
-		</label>
+  <div ng-if="!ctrl.hideEditorRowActions" class="gf-form">
+    <label class="gf-form-label dropdown">
+      <a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1"> <i class="fa fa-bars"></i> </a>
+      <ul class="dropdown-menu pull-right" role="menu">
+        <li role="menuitem" ng-if="ctrl.hasTextEditMode">
+          <a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
+        </li>
+        <li role="menuitem"><a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a></li>
+        <li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a></li>
+        <li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a></li>
+      </ul>
+    </label>
+    <label class="gf-form-label">
+      <a ng-click="ctrl.toggleHideQuery()" role="menuitem"> <i class="fa fa-eye"></i> </a>
+    </label>
     <label class="gf-form-label">
-			<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
-				<i class="fa fa-eye"></i>
-			</a>
-		</label>
-		<label class="gf-form-label">
-			<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
-				<i class="fa fa-trash"></i>
-			</a>
-		</label>
-	</div>
+      <a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)"> <i class="fa fa-trash"></i> </a>
+    </label>
+  </div>
 </div>
-

+ 2 - 0
public/app/features/panel/query_editor_row.ts

@@ -11,11 +11,13 @@ export class QueryRowCtrl {
   panelCtrl: any;
   panel: any;
   collapsed: any;
+  hideEditorRowActions: boolean;
 
   constructor() {
     this.panelCtrl = this.queryCtrl.panelCtrl;
     this.target = this.queryCtrl.target;
     this.panel = this.panelCtrl.panel;
+    this.hideEditorRowActions = this.panelCtrl.hideEditorRowActions;
 
     if (!this.target.refId) {
       this.target.refId = this.panelCtrl.dashboard.getNextQueryLetter(this.panel);

+ 2 - 9
public/app/features/plugins/datasource_srv.ts

@@ -74,7 +74,8 @@ export class DatasourceSrv {
   }
 
   getAll() {
-    return config.datasources;
+    const { datasources } = config;
+    return Object.keys(datasources).map(name => datasources[name]);
   }
 
   getAnnotationSources() {
@@ -91,14 +92,6 @@ export class DatasourceSrv {
     return sources;
   }
 
-  getExploreSources() {
-    const { datasources } = config;
-    const es = Object.keys(datasources)
-      .map(name => datasources[name])
-      .filter(ds => ds.meta && ds.meta.explore);
-    return _.sortBy(es, ['name']);
-  }
-
   getMetricSources(options?) {
     const metricSources: DataSourceSelectItem[] = [];
 

+ 0 - 26
public/app/features/plugins/specs/datasource_srv.test.ts

@@ -18,32 +18,6 @@ const templateSrv = {
 describe('datasource_srv', () => {
   const _datasourceSrv = new DatasourceSrv({}, {}, {}, templateSrv);
 
-  describe('when loading explore sources', () => {
-    beforeEach(() => {
-      config.datasources = {
-        explore1: {
-          name: 'explore1',
-          meta: { explore: true, metrics: true },
-        },
-        explore2: {
-          name: 'explore2',
-          meta: { explore: true, metrics: false },
-        },
-        nonExplore: {
-          name: 'nonExplore',
-          meta: { explore: false, metrics: true },
-        },
-      };
-    });
-
-    it('should return list of explore sources', () => {
-      const exploreSources = _datasourceSrv.getExploreSources();
-      expect(exploreSources.length).toBe(2);
-      expect(exploreSources[0].name).toBe('explore1');
-      expect(exploreSources[1].name).toBe('explore2');
-    });
-  });
-
   describe('when loading metric sources', () => {
     let metricSources;
     const unsortedDatasources = {

+ 7 - 5
public/app/plugins/datasource/graphite/plugin.json

@@ -3,13 +3,12 @@
   "type": "datasource",
   "id": "graphite",
 
-  "includes": [
-    {"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"}
-  ],
+  "includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
 
   "metrics": true,
   "alerting": true,
   "annotations": true,
+  "tables": false,
 
   "queryOptions": {
     "maxDataPoints": true,
@@ -27,8 +26,11 @@
       "large": "img/graphite_logo.png"
     },
     "links": [
-      {"name": "Graphite", "url": "https://graphiteapp.org/"},
-      {"name": "Graphite 1.1 Release", "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"}
+      { "name": "Graphite", "url": "https://graphiteapp.org/" },
+      {
+        "name": "Graphite 1.1 Release",
+        "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"
+      }
     ],
     "version": "5.0.0"
   }

+ 2 - 1
public/app/plugins/datasource/logging/plugin.json

@@ -7,6 +7,7 @@
   "annotations": false,
   "logs": true,
   "explore": true,
+  "tables": true,
   "info": {
     "description": "Grafana Logging Data Source for Grafana",
     "author": {
@@ -25,4 +26,4 @@
     ],
     "version": "5.3.0"
   }
-}
+}

+ 1 - 1
public/app/plugins/datasource/mssql/plugin.json

@@ -18,9 +18,9 @@
   "alerting": true,
   "annotations": true,
   "metrics": true,
+  "tables": true,
 
   "queryOptions": {
     "minInterval": true
   }
-
 }

+ 1 - 1
public/app/plugins/datasource/mysql/plugin.json

@@ -19,9 +19,9 @@
   "alerting": true,
   "annotations": true,
   "metrics": true,
+  "tables": true,
 
   "queryOptions": {
     "minInterval": true
   }
-
 }

+ 1 - 0
public/app/plugins/datasource/opentsdb/plugin.json

@@ -7,6 +7,7 @@
   "defaultMatchFormat": "pipe",
   "annotations": true,
   "alerting": true,
+  "tables": false,
 
   "info": {
     "description": "OpenTSDB Data Source for Grafana",

+ 1 - 1
public/app/plugins/datasource/postgres/plugin.json

@@ -19,9 +19,9 @@
   "alerting": true,
   "annotations": true,
   "metrics": true,
+  "tables": true,
 
   "queryOptions": {
     "minInterval": true
   }
-
 }

+ 2 - 1
public/app/plugins/datasource/prometheus/plugin.json

@@ -23,6 +23,7 @@
   "alerting": true,
   "annotations": true,
   "explore": true,
+  "tables": true,
   "queryOptions": {
     "minInterval": true
   },
@@ -44,4 +45,4 @@
     ],
     "version": "5.0.0"
   }
-}
+}

+ 1 - 0
public/app/plugins/datasource/stackdriver/plugin.json

@@ -5,6 +5,7 @@
   "metrics": true,
   "alerting": true,
   "annotations": true,
+  "tables": false,
   "state": "beta",
   "queryOptions": {
     "maxDataPoints": true,

+ 7 - 0
public/app/plugins/datasource/testdata/datasource.ts

@@ -84,6 +84,13 @@ class TestDataDatasource {
     }
     return this.$q.when(events);
   }
+
+  testDatasource() {
+    return Promise.resolve({
+      status: 'success',
+      message: 'Data source is working',
+    });
+  }
 }
 
 export { TestDataDatasource };

+ 7 - 12
public/app/plugins/panel/graph/module.ts

@@ -5,8 +5,8 @@ import './time_regions_form';
 
 import template from './template';
 import _ from 'lodash';
-import config from 'app/core/config';
-import { MetricsPanelCtrl, alertTab } from 'app/plugins/sdk';
+
+import { MetricsPanelCtrl } from 'app/plugins/sdk';
 import { DataProcessor } from './data_processor';
 import { axesEditorComponent } from './axes_editor';
 
@@ -71,11 +71,11 @@ class GraphCtrl extends MetricsPanelCtrl {
     // length of a dash
     dashLength: 10,
     // length of space between two dashes
-    spaceLength: 10,
+    paceLength: 10,
     // show hide points
     points: false,
     // point radius in pixels
-    pointradius: 5,
+    pointradius: 2,
     // show hide bars
     bars: false,
     // enable/disable stacking
@@ -135,14 +135,9 @@ class GraphCtrl extends MetricsPanelCtrl {
   }
 
   onInitEditMode() {
-    this.addEditorTab('Axes', axesEditorComponent, 2);
-    this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3);
-    this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
-
-    if (config.alertingEnabled) {
-      this.addEditorTab('Alert', alertTab, 5);
-    }
-
+    this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html');
+    this.addEditorTab('Axes', axesEditorComponent);
+    this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html');
     this.subTabIndex = 0;
   }
 

+ 8 - 0
public/app/types/form.ts

@@ -0,0 +1,8 @@
+export interface ValidationRule {
+  rule: (valueToValidate: string) => boolean;
+  errorMessage: string;
+}
+
+export interface ValidationEvents {
+  [eventName: string]: ValidationRule[];
+}

+ 3 - 1
public/app/types/index.ts

@@ -30,7 +30,7 @@ import {
   AppNotificationTimeout,
 } from './appNotifications';
 import { DashboardSearchHit } from './search';
-
+import { ValidationEvents, ValidationRule } from './form';
 export {
   Team,
   TeamsState,
@@ -89,6 +89,8 @@ export {
   AppNotificationTimeout,
   DashboardSearchHit,
   UserState,
+  ValidationEvents,
+  ValidationRule,
 };
 
 export interface StoreState {

+ 10 - 0
public/app/types/plugins.ts

@@ -27,6 +27,12 @@ export interface PanelPlugin {
   exports?: PluginExports;
 }
 
+interface PluginMetaQueryOptions {
+  cacheTimeout?: boolean;
+  maxDataPoints?: boolean;
+  minInterval?: boolean;
+}
+
 export interface PluginMeta {
   id: string;
   name: string;
@@ -35,9 +41,13 @@ export interface PluginMeta {
 
   // Datasource-specific
   metrics?: boolean;
+  tables?: boolean;
   logs?: boolean;
   explore?: boolean;
   annotations?: boolean;
+  mixed?: boolean;
+  hasQueryHelp?: boolean;
+  queryOptions?: PluginMetaQueryOptions;
 }
 
 export interface PluginInclude {

+ 33 - 0
public/img/icons_dark_theme/icon_advanced.svg

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#0A0A0C;}
+	.st1{fill:#E3E2E2;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+	<g>
+		<path class="st1" d="M58.2,51.9c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
+			c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
+			c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
+			L58.2,51.9z M67.9,71c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5s-1.7-1.1-1.5-2.1C65.9,71.6,66.9,70.9,67.9,71z"/>
+		<path class="st1" d="M61.5,50.9c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.4,51.1,61.5,50.9z"/>
+		<path class="st1" d="M59.9,41.6c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.7,59.9,41.6z"/>
+		<path class="st1" d="M61.6,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
+			C61.5,44.3,62.1,47.2,61.6,50.1z"/>
+		<path class="st1" d="M59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3z"/>
+		<path class="st1" d="M73.4,59.1l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
+			c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
+			l-4.8-5.1L32,28.9l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.2L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
+			l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.3l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
+			l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
+			c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
+			c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.1z"/>
+	</g>
+</g>
+</svg>

+ 69 - 0
public/img/icons_dark_theme/icon_advanced_active.svg

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#0A0A0C;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+	.st4{fill:url(#SVGID_4_);}
+	.st5{fill:url(#SVGID_5_);}
+	.st6{fill:url(#SVGID_6_);}
+	.st7{fill:url(#SVGID_7_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.4,49.4C94.4,25,74.6,5.1,50.2,5.1S5.9,25,5.9,49.4s19.9,44.3,44.3,44.3S94.4,73.8,94.4,49.4z"/>
+	<g>
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="56.3802" y1="134.9514" x2="56.3802" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st2" d="M58.4,51.3c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
+			c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
+			c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
+			L58.4,51.3z M68.1,70.4c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5S65.8,73,66,72C66.1,70.9,67.1,70.3,68.1,70.4z"/>
+		<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.6059" y1="134.9514" x2="61.6059" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st3" d="M61.7,50.3c0,0.1-0.1,0.3-0.1,0.4C61.6,50.6,61.6,50.4,61.7,50.3z"/>
+		<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="60.1683" y1="134.9514" x2="60.1683" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st4" d="M60.1,41c0.1,0.1,0.1,0.2,0.2,0.3C60.2,41.2,60.1,41.1,60.1,41z"/>
+		<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="61.1232" y1="134.9514" x2="61.1232" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st5" d="M61.8,49.5c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
+			C61.7,43.7,62.3,46.5,61.8,49.5z"/>
+		<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="59.8929" y1="134.9514" x2="59.8929" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st6" d="M59.9,40.7C59.9,40.7,59.9,40.8,59.9,40.7C59.9,40.8,59.9,40.7,59.9,40.7z"/>
+		<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="49.8648" y1="134.9514" x2="49.8648" y2="30.9934">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st7" d="M73.6,58.5l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7L66.7,38
+			c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
+			l-4.8-5.1l-4.9,3.5l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5l-6.9-1.3l-1.9,5.7l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
+			l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5l-3.4,6.2l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
+			l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
+			c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
+			c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.6,58.5z"/>
+	</g>
+</g>
+</svg>

+ 26 - 0
public/img/icons_dark_theme/icon_alerting.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#0A0A0C;}
+	.st1{fill:#E3E2E2;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+	<g>
+		<g>
+			<g>
+				<path class="st1" d="M49.3,77.8c4,0,7.3-3.3,7.3-7.3H42C42,74.5,45.3,77.8,49.3,77.8z"/>
+			</g>
+		</g>
+		<g>
+			<path class="st1" d="M68.2,59v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
+				c-8.1,2.8-14,12.3-14,23.5V59c-2.2,0-4,1.8-4,4s1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4S70.4,59.1,68.2,59z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 43 - 0
public/img/icons_dark_theme/icon_alerting_active.svg

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#0A0A0C;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.4,49.5c0-24.4-19.9-44.3-44.3-44.3S5.8,25.1,5.8,49.5s19.9,44.3,44.3,44.3S94.4,73.9,94.4,49.5z"/>
+	<g>
+		<g>
+			<g>
+				<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
+					<stop  offset="0" style="stop-color:#FFF100"/>
+					<stop  offset="1" style="stop-color:#F05A28"/>
+				</linearGradient>
+				<path class="st2" d="M49.4,77.3c4,0,7.3-3.3,7.3-7.3H42.1C42.1,74.1,45.4,77.3,49.4,77.3z"/>
+			</g>
+		</g>
+		<g>
+			<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
+				<stop  offset="0" style="stop-color:#FFF100"/>
+				<stop  offset="1" style="stop-color:#F05A28"/>
+			</linearGradient>
+			<path class="st3" d="M68.3,58.6v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
+				c-8.1,2.8-14,12.3-14,23.5v9.2c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4
+				C72.3,60.4,70.5,58.6,68.3,58.6z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 25 - 0
public/img/icons_dark_theme/icon_query.svg

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#0A0A0C;}
+	.st1{fill:#E3E2E2;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z M49.1,73.7
+		c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
+		C74.8,68,63.1,73.7,49.1,73.7z M49.1,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3
+		s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2C74.8,56.9,63.1,62.5,49.1,62.5z M49.1,51.3c-13.9,0-25.7-5.6-25.7-12.3
+		c0-6.7,11.8-12.3,25.7-12.3S74.8,32.4,74.8,39C74.8,45.7,63.1,51.3,49.1,51.3z"/>
+	<path class="st1" d="M49.1,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S74.8,68,74.8,61.4
+		c0-0.7-0.1-1.3-0.4-2C70.1,64.4,60.5,67.7,49.1,67.7z"/>
+	<path class="st1" d="M49.1,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3s25.7-5.6,25.7-12.3
+		c0-0.7-0.1-1.3-0.4-2C70.1,53.2,60.5,56.5,49.1,56.5z"/>
+	<path class="st1" d="M49.1,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S74.8,45.7,74.8,39
+		C74.8,32.4,63.1,26.7,49.1,26.7z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+</g>
+</svg>

+ 46 - 0
public/img/icons_dark_theme/icon_query_active.svg

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#0A0A0C;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+	.st4{fill:url(#SVGID_4_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.4,50c0-24.4-19.9-44.3-44.3-44.3S5.9,25.6,5.9,50s19.9,44.3,44.3,44.3S94.4,74.4,94.4,50z M49.3,73.7
+		c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
+		C75,68,63.2,73.7,49.3,73.7z M49.3,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3
+		c0.2,0.6,0.4,1.3,0.4,2C75,56.9,63.2,62.5,49.3,62.5z M49.3,51.3c-13.9,0-25.7-5.6-25.7-12.3c0-6.7,11.8-12.3,25.7-12.3
+		S75,32.4,75,39C75,45.7,63.2,51.3,49.3,51.3z"/>
+	<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.777" x2="49.2981" y2="18.669">
+		<stop  offset="0" style="stop-color:#FFF100"/>
+		<stop  offset="1" style="stop-color:#F05A28"/>
+	</linearGradient>
+	<path class="st2" d="M49.3,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,68,75,61.4
+		c0-0.7-0.1-1.3-0.4-2C70.2,64.4,60.7,67.7,49.3,67.7z"/>
+	<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
+		<stop  offset="0" style="stop-color:#FFF100"/>
+		<stop  offset="1" style="stop-color:#F05A28"/>
+	</linearGradient>
+	<path class="st3" d="M49.3,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,56.9,75,50.2
+		c0-0.7-0.1-1.3-0.4-2C70.2,53.2,60.7,56.5,49.3,56.5z"/>
+	<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
+		<stop  offset="0" style="stop-color:#FFF100"/>
+		<stop  offset="1" style="stop-color:#F05A28"/>
+	</linearGradient>
+	<path class="st4" d="M49.3,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S75,45.7,75,39C75,32.4,63.2,26.7,49.3,26.7z
+		"/>
+</g>
+</svg>

+ 20 - 0
public/img/icons_dark_theme/icon_visualize.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#0A0A0C;}
+	.st1{fill:#E3E2E2;}
+</style>
+<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+	S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+<g>
+	<path class="st1" d="M77.6,66.3H25.2V30.9c0-1.5-1.2-2.8-2.8-2.8s-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
+		c1.5,0,2.8-1.2,2.8-2.8S79.1,66.3,77.6,66.3z"/>
+	<g>
+		<polygon class="st1" points="77.5,63.6 77.8,49.5 62.7,28.8 46.8,50 35.6,39.2 27.9,43.6 27.9,63.6 		"/>
+	</g>
+</g>
+</svg>

+ 38 - 0
public/img/icons_dark_theme/icon_visualize_active.svg

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#0A0A0C;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.1,50c0-24.4-19.9-44.3-44.3-44.3S5.6,25.6,5.6,50s19.9,44.3,44.3,44.3S94.1,74.4,94.1,50z"/>
+	<g>
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="50.6946" y1="113.8319" x2="50.6946" y2="21.7994">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st2" d="M78.3,65.9H25.9V30.5c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
+			c1.5,0,2.8-1.2,2.8-2.8S79.8,65.9,78.3,65.9z"/>
+		<g>
+			<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="53.553" y1="113.8319" x2="53.553" y2="21.7994">
+				<stop  offset="0" style="stop-color:#FFF100"/>
+				<stop  offset="1" style="stop-color:#F05A28"/>
+			</linearGradient>
+			<polygon class="st3" points="78.2,63.1 78.5,49 63.4,28.3 47.5,49.5 36.3,38.7 28.6,43.1 28.6,63.1 			"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 33 - 0
public/img/icons_light_theme/icon_advanced.svg

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.st1{fill:#52545c;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+	<g>
+		<path class="st1" d="M58.2,51.9c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
+			c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
+			c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
+			L58.2,51.9z M67.9,71c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5s-1.7-1.1-1.5-2.1C65.9,71.6,66.9,70.9,67.9,71z"/>
+		<path class="st1" d="M61.5,50.9c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.4,51.1,61.5,50.9z"/>
+		<path class="st1" d="M59.9,41.6c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.7,59.9,41.6z"/>
+		<path class="st1" d="M61.6,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
+			C61.5,44.3,62.1,47.2,61.6,50.1z"/>
+		<path class="st1" d="M59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3z"/>
+		<path class="st1" d="M73.4,59.1l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
+			c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
+			l-4.8-5.1L32,28.9l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.2L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
+			l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.3l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
+			l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
+			c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
+			c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.1z"/>
+	</g>
+</g>
+</svg>

+ 70 - 0
public/img/icons_light_theme/icon_advanced_active.svg

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#FFFFFF;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+	.st4{fill:url(#SVGID_4_);}
+	.st5{fill:url(#SVGID_5_);}
+	.st6{fill:url(#SVGID_6_);}
+	.st7{fill:url(#SVGID_7_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.3,50.1C94.3,25.7,74.4,5.8,50,5.8S5.8,25.7,5.8,50.1S25.6,94.3,50,94.3S94.3,74.5,94.3,50.1z"/>
+	<g>
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="56.209" y1="135.6298" x2="56.209" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st2" d="M58.2,52c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
+			c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
+			c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9c1.2,0.2,2.5-0.1,3.5-0.9
+			c2.1-1.5,2.6-4.5,1-6.6L58.2,52z M67.9,71.1c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5c-1-0.2-1.7-1.1-1.5-2.1
+			C65.9,71.6,66.9,70.9,67.9,71.1z"/>
+		<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.4347" y1="135.6298" x2="61.4347" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st3" d="M61.5,51c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.5,51.1,61.5,51z"/>
+		<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="59.9971" y1="135.6298" x2="59.9971" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st4" d="M59.9,41.7c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.8,59.9,41.7z"/>
+		<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="60.952" y1="135.6298" x2="60.952" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st5" d="M61.7,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
+			C61.5,44.4,62.1,47.2,61.7,50.1z"/>
+		<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="59.7217" y1="135.6298" x2="59.7217" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FFF100"/>
+			<stop  offset="1" style="stop-color:#F05A28"/>
+		</linearGradient>
+		<path class="st6" d="M59.7,41.4C59.7,41.4,59.7,41.4,59.7,41.4C59.7,41.4,59.7,41.4,59.7,41.4z"/>
+		<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="49.6936" y1="135.6298" x2="49.6936" y2="31.6718">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st7" d="M73.4,59.2l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
+			c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
+			l-4.8-5.1L32,29l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.3L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
+			l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.4l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
+			l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
+			c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
+			c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.2z"/>
+	</g>
+</g>
+</svg>

+ 26 - 0
public/img/icons_light_theme/icon_alerting.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.st1{fill:#52545c;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+	<g>
+		<g>
+			<g>
+				<path class="st1" d="M49.3,77.8c4,0,7.3-3.3,7.3-7.3H42C42,74.5,45.3,77.8,49.3,77.8z"/>
+			</g>
+		</g>
+		<g>
+			<path class="st1" d="M68.2,59v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
+				c-8.1,2.8-14,12.3-14,23.5V59c-2.2,0-4,1.8-4,4s1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4S70.4,59.1,68.2,59z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 43 - 0
public/img/icons_light_theme/icon_alerting_active.svg

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#FFFFFF;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.4,49.5c0-24.4-19.9-44.3-44.3-44.3S5.8,25.1,5.8,49.5s19.9,44.3,44.3,44.3S94.4,73.9,94.4,49.5z"/>
+	<g>
+		<g>
+			<g>
+				<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
+					<stop  offset="0" style="stop-color:#FBB017"/>
+					<stop  offset="1" style="stop-color:#EF4E28"/>
+				</linearGradient>
+				<path class="st2" d="M49.4,77.3c4,0,7.3-3.3,7.3-7.3H42.1C42.1,74.1,45.4,77.3,49.4,77.3z"/>
+			</g>
+		</g>
+		<g>
+			<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
+				<stop  offset="0" style="stop-color:#FBB017"/>
+				<stop  offset="1" style="stop-color:#EF4E28"/>
+			</linearGradient>
+			<path class="st3" d="M68.3,58.6v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
+				c-8.1,2.8-14,12.3-14,23.5v9.2c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4
+				C72.3,60.4,70.5,58.6,68.3,58.6z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 25 - 0
public/img/icons_light_theme/icon_query.svg

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.st1{fill:#52545c;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z M49.1,73.7
+		c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
+		C74.8,68,63.1,73.7,49.1,73.7z M49.1,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3
+		s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2C74.8,56.9,63.1,62.5,49.1,62.5z M49.1,51.3c-13.9,0-25.7-5.6-25.7-12.3
+		c0-6.7,11.8-12.3,25.7-12.3S74.8,32.4,74.8,39C74.8,45.7,63.1,51.3,49.1,51.3z"/>
+	<path class="st1" d="M49.1,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S74.8,68,74.8,61.4
+		c0-0.7-0.1-1.3-0.4-2C70.1,64.4,60.5,67.7,49.1,67.7z"/>
+	<path class="st1" d="M49.1,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3s25.7-5.6,25.7-12.3
+		c0-0.7-0.1-1.3-0.4-2C70.1,53.2,60.5,56.5,49.1,56.5z"/>
+	<path class="st1" d="M49.1,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S74.8,45.7,74.8,39
+		C74.8,32.4,63.1,26.7,49.1,26.7z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+</g>
+</svg>

+ 46 - 0
public/img/icons_light_theme/icon_query_active.svg

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#FFFFFF;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+	.st4{fill:url(#SVGID_4_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.4,50c0-24.4-19.9-44.3-44.3-44.3S5.9,25.6,5.9,50s19.9,44.3,44.3,44.3S94.4,74.4,94.4,50z M49.3,73.7
+		c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
+		C75,68,63.2,73.7,49.3,73.7z M49.3,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3
+		c0.2,0.6,0.4,1.3,0.4,2C75,56.9,63.2,62.5,49.3,62.5z M49.3,51.3c-13.9,0-25.7-5.6-25.7-12.3c0-6.7,11.8-12.3,25.7-12.3
+		S75,32.4,75,39C75,45.7,63.2,51.3,49.3,51.3z"/>
+	<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.777" x2="49.2981" y2="18.669">
+		<stop  offset="0" style="stop-color:#FBB017"/>
+		<stop  offset="1" style="stop-color:#EF4E28"/>
+	</linearGradient>
+	<path class="st2" d="M49.3,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,68,75,61.4
+		c0-0.7-0.1-1.3-0.4-2C70.2,64.4,60.7,67.7,49.3,67.7z"/>
+	<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
+		<stop  offset="0" style="stop-color:#FBB017"/>
+		<stop  offset="1" style="stop-color:#EF4E28"/>
+	</linearGradient>
+	<path class="st3" d="M49.3,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,56.9,75,50.2
+		c0-0.7-0.1-1.3-0.4-2C70.2,53.2,60.7,56.5,49.3,56.5z"/>
+	<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
+		<stop  offset="0" style="stop-color:#FBB017"/>
+		<stop  offset="1" style="stop-color:#EF4E28"/>
+	</linearGradient>
+	<path class="st4" d="M49.3,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S75,45.7,75,39C75,32.4,63.2,26.7,49.3,26.7z
+		"/>
+</g>
+</svg>

+ 22 - 0
public/img/icons_light_theme/icon_visualize.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.st1{fill:#52545c;}
+</style>
+<g>
+	<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
+	<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
+		S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
+	<g>
+		<path class="st1" d="M78,65.8H25.6V30.4c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8H78
+			c1.5,0,2.8-1.2,2.8-2.8C80.7,67.1,79.5,65.8,78,65.8z"/>
+		<g>
+			<polygon class="st1" points="77.9,63.1 78.2,49 63.1,28.3 47.2,49.5 36,38.7 28.3,43.1 28.3,63.1 			"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 38 - 0
public/img/icons_light_theme/icon_visualize_active.svg

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, 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="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:#FFFFFF;}
+	.st2{fill:url(#SVGID_2_);}
+	.st3{fill:url(#SVGID_3_);}
+</style>
+<g>
+	<g>
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
+			c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
+	</g>
+	<path class="st1" d="M94.1,50c0-24.4-19.9-44.3-44.3-44.3S5.6,25.6,5.6,50s19.9,44.3,44.3,44.3S94.1,74.4,94.1,50z"/>
+	<g>
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="50.2576" y1="115.6711" x2="50.2576" y2="19.8747">
+			<stop  offset="0" style="stop-color:#FBB017"/>
+			<stop  offset="1" style="stop-color:#EF4E28"/>
+		</linearGradient>
+		<path class="st2" d="M77.8,65.9H25.4V30.5c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
+			c1.5,0,2.8-1.2,2.8-2.8S79.4,65.9,77.8,65.9z"/>
+		<g>
+			<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="53.116" y1="115.6711" x2="53.116" y2="19.8747">
+				<stop  offset="0" style="stop-color:#FBB017"/>
+				<stop  offset="1" style="stop-color:#EF4E28"/>
+			</linearGradient>
+			<polygon class="st3" points="77.7,63.1 78,49 62.9,28.3 47.1,49.5 35.8,38.7 28.2,43.1 28.2,63.1 			"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 9 - 0
public/img/panel-tabs/alert-selected.svg

@@ -0,0 +1,9 @@
+<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
+<line x1="12.5925" y1="28.1482" x2="31.4073" y2="28.1482" stroke="#8E8E8E" stroke-width="4" stroke-linecap="round"/>
+<ellipse cx="22.0001" cy="20.3704" rx="8.96296" ry="9.77778" fill="#8E8E8E"/>
+<circle cx="22.0001" cy="30.1482" r="2.44444" fill="#8E8E8E"/>
+<line x1="18.7407" y1="30.463" x2="25.2592" y2="30.463" stroke="#09090B"/>
+<rect x="13.0371" y="20.3704" width="17.9259" height="6.51852" fill="#8E8E8E"/>
+<ellipse cx="22.0001" cy="11.0001" rx="0.814815" ry="1.22222" fill="#8E8E8E"/>
+</svg>

+ 9 - 0
public/img/panel-tabs/alert.svg

@@ -0,0 +1,9 @@
+<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
+<line x1="12.5925" y1="28.1482" x2="31.4073" y2="28.1482" stroke="#8E8E8E" stroke-width="4" stroke-linecap="round"/>
+<ellipse cx="22.0001" cy="20.3704" rx="8.96296" ry="9.77778" fill="#8E8E8E"/>
+<circle cx="22.0001" cy="30.1482" r="2.44444" fill="#8E8E8E"/>
+<line x1="18.7407" y1="30.463" x2="25.2592" y2="30.463" stroke="#09090B"/>
+<rect x="13.0371" y="20.3704" width="17.9259" height="6.51852" fill="#8E8E8E"/>
+<ellipse cx="22.0001" cy="11.0001" rx="0.814815" ry="1.22222" fill="#8E8E8E"/>
+</svg>

+ 31 - 0
public/img/panel-tabs/general-selected.svg

@@ -0,0 +1,31 @@
+<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
+<ellipse cx="27" cy="33" rx="14" ry="6" fill="url(#paint1_linear)"/>
+<ellipse cx="27" cy="30" rx="14" ry="6" fill="#09090B"/>
+<ellipse cx="27" cy="27" rx="14" ry="6" fill="url(#paint2_linear)"/>
+<ellipse cx="27" cy="24" rx="14" ry="6" fill="#09090B"/>
+<ellipse cx="27" cy="21" rx="14" ry="6" fill="url(#paint3_linear)"/>
+<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint4_linear)"/>
+<defs>
+<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint1_linear" x1="27" y1="2.01165e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint2_linear" x1="27" y1="1.40816e-06" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint3_linear" x1="27" y1="-6.03497e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint4_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EB7B18"/>
+<stop offset="1" stop-color="#D44A3A"/>
+</linearGradient>
+</defs>
+</svg>

+ 8 - 0
public/img/panel-tabs/general.svg

@@ -0,0 +1,8 @@
+<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
+<ellipse cx="21.9999" cy="26.8889" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+<ellipse cx="21.9999" cy="24.4444" rx="11.4074" ry="4.88889" fill="#09090B"/>
+<ellipse cx="21.9999" cy="22" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+<ellipse cx="21.9999" cy="19.5555" rx="11.4074" ry="4.88889" fill="#09090B"/>
+<ellipse cx="21.9999" cy="17.1111" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+</svg>

+ 31 - 0
public/img/panel-tabs/queries-selected.svg

@@ -0,0 +1,31 @@
+<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
+<ellipse cx="27" cy="33" rx="14" ry="6" fill="url(#paint1_linear)"/>
+<ellipse cx="27" cy="30" rx="14" ry="6" fill="#09090B"/>
+<ellipse cx="27" cy="27" rx="14" ry="6" fill="url(#paint2_linear)"/>
+<ellipse cx="27" cy="24" rx="14" ry="6" fill="#09090B"/>
+<ellipse cx="27" cy="21" rx="14" ry="6" fill="url(#paint3_linear)"/>
+<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint4_linear)"/>
+<defs>
+<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint1_linear" x1="27" y1="2.01165e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint2_linear" x1="27" y1="1.40816e-06" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint3_linear" x1="27" y1="-6.03497e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#FEBC11"/>
+</linearGradient>
+<linearGradient id="paint4_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EB7B18"/>
+<stop offset="1" stop-color="#D44A3A"/>
+</linearGradient>
+</defs>
+</svg>

+ 8 - 0
public/img/panel-tabs/queries.svg

@@ -0,0 +1,8 @@
+<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
+<ellipse cx="21.9999" cy="26.8889" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+<ellipse cx="21.9999" cy="24.4444" rx="11.4074" ry="4.88889" fill="#09090B"/>
+<ellipse cx="21.9999" cy="22" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+<ellipse cx="21.9999" cy="19.5555" rx="11.4074" ry="4.88889" fill="#09090B"/>
+<ellipse cx="21.9999" cy="17.1111" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
+</svg>

+ 34 - 0
public/img/panel-tabs/visualization-selected.svg

@@ -0,0 +1,34 @@
+<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
+<path d="M39 21L35 17H39V21Z" fill="url(#paint1_linear)"/>
+<line x1="14" y1="15" x2="14" y2="39" stroke="url(#paint2_linear)" stroke-width="2"/>
+<line x1="41" y1="38" x2="14" y2="38" stroke="url(#paint3_linear)" stroke-width="2"/>
+<path d="M37 19L28 27L25 24L16 33" stroke="url(#paint4_linear)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint5_linear)"/>
+<defs>
+<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint1_linear" x1="37" y1="-9.62518e-07" x2="37" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint2_linear" x1="13" y1="53.5" x2="13" y2="1.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EB7B18"/>
+<stop offset="1" stop-color="#D44A3A"/>
+</linearGradient>
+<linearGradient id="paint3_linear" x1="27" y1="7.21113e-07" x2="27.5" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint4_linear" x1="27" y1="-4.4462e-08" x2="27" y2="54" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D44A3A"/>
+<stop offset="1" stop-color="#EB7B18"/>
+</linearGradient>
+<linearGradient id="paint5_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EB7B18"/>
+<stop offset="1" stop-color="#D44A3A"/>
+</linearGradient>
+</defs>
+</svg>

+ 7 - 0
public/img/panel-tabs/visualization.svg

@@ -0,0 +1,7 @@
+<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
+<path d="M31.7778 17.1111L28.5186 13.8518H31.7778V17.1111Z" fill="#8E8E8E"/>
+<line x1="11" y1="12" x2="11" y2="32" stroke="#8E8E8E" stroke-width="2"/>
+<line x1="33" y1="31" x2="11" y2="31" stroke="#8E8E8E" stroke-width="2"/>
+<path d="M30.1482 15.4814L22.8149 22L20.3704 19.5555L13.0371 26.8889" stroke="#8E8E8E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 20 - 4
public/sass/_variables.dark.scss

@@ -79,6 +79,7 @@ $brand-gradient: linear-gradient(
 );
 
 $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
+$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909);
 
 // Links
 // -------------------------
@@ -268,9 +269,7 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black;
 $tab-border-color: $dark-4;
 
 // Toolbar
-$toolbar-bg: $page-header-bg;
-$toolbar-shadow: 0 0 20px black;
-$toolbar-tab-bg: $gray-blue;
+$toolbar-bg: $black;
 
 // Pagination
 // -------------------------
@@ -367,6 +366,23 @@ $switch-slider-shadow: 0 0 3px black;
 //Checkbox
 // -------------------------
 $checkbox-bg: $dark-1;
-$checkbox-border: 1px solid $gray-2;
+$checkbox-border: 1px solid $gray-1;
 $checkbox-checked-bg: linear-gradient(0deg, $orange, $red);
 $checkbox-color: $dark-1;
+
+//Panel Edit
+// -------------------------
+$panel-editor-shadow: 0 0 20px black;
+$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
+$panel-editor-toolbar-view-bg: $black;
+$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
+$panel-editor-viz-item-border: 1px solid $dark-5;
+$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
+$panel-editor-viz-item-border-hover: 1px solid $blue;
+$panel-editor-viz-item-bg: $black;
+$panel-editor-tabs-line-color: #e3e3e3;
+$panel-editor-viz-item-bg-hover: darken($blue, 47%);
+$panel-editor-viz-item-bg-hover-active: darken($orange, 45%);
+
+$panel-grid-placeholder-bg: darken($blue, 47%);
+$panel-grid-placeholder-shadow: 0 0 4px $blue;

+ 21 - 4
public/sass/_variables.light.scss

@@ -61,7 +61,7 @@ $critical: #ec2128;
 // -------------------------
 
 $body-bg: $gray-7;
-$page-bg: $gray-7;
+$page-bg: $gray-6;
 $body-color: $gray-1;
 $text-color: $gray-1;
 $text-color-strong: $dark-2;
@@ -76,6 +76,8 @@ $textShadow: none;
 // gradients
 $brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
 $page-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
+//$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
+$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
 
 // Links
 // -------------------------
@@ -215,9 +217,7 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
 $tab-border-color: $gray-5;
 
 // Toolbar
-$toolbar-bg: linear-gradient(90deg, #ffffff, #e6eef9);
-$toolbar-shadow: 1px 1px 3px #c7d0d8;
-$toolbar-tab-bg: $white;
+$toolbar-bg: white;
 
 // search
 $search-shadow: 0 5px 30px 0 $gray-4;
@@ -379,3 +379,20 @@ $checkbox-bg: $gray-6;
 $checkbox-border: 1px solid $gray-3;
 $checkbox-checked-bg: linear-gradient(0deg, $yellow, $red);
 $checkbox-color: $gray-7;
+
+//Panel Edit
+// -------------------------
+$panel-editor-shadow: 2px 2px 8px $gray-3;
+$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
+$panel-editor-toolbar-view-bg: $white;
+$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
+$panel-editor-viz-item-border: 1px solid $gray-3;
+$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
+$panel-editor-viz-item-border-hover: 1px solid $blue-light;
+$panel-editor-viz-item-bg: $white;
+$panel-editor-tabs-line-color: $dark-5;
+$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
+$panel-editor-viz-item-bg-hover-active: lighten($orange, 34%);
+
+$panel-grid-placeholder-bg: lighten($blue, 62%);
+$panel-grid-placeholder-shadow: 0 0 4px $blue-light;

+ 34 - 0
public/sass/base/_icons.scss

@@ -211,3 +211,37 @@
   min-width: 20px;
   padding-right: 5px;
 }
+
+.panel-editor-tabs {
+  .gicon-advanced-active {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg');
+  }
+
+  .gicon-advanced {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced.svg');
+  }
+
+  .gicon-alerts-active {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_alerting_active.svg');
+  }
+
+  .gicon-alerts {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_alerting.svg');
+  }
+
+  .gicon-queries-active {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_query_active.svg');
+  }
+
+  .gicon-queries {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_query.svg');
+  }
+
+  .gicon-visualization-active {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_visualize_active.svg');
+  }
+
+  .gicon-visualization {
+    background-image: url('../img/icons_#{$theme-name}_theme/icon_visualize.svg');
+  }
+}

+ 6 - 0
public/sass/components/_buttons.scss

@@ -172,6 +172,12 @@
   padding-right: 20px;
 }
 
+// No horizontal padding
+.btn-p-x-0 {
+  padding-left: 0;
+  padding-right: 0;
+}
+
 // External services
 // Usage:
 // <div class="btn btn-service btn-service--facebook">Button text</div>

+ 16 - 13
public/sass/components/_dashboard_grid.scss

@@ -9,18 +9,14 @@
   .react-grid-item {
     display: none !important;
     transition-property: none !important;
-  }
 
-  .panel--fullscreen {
-    display: block !important;
-    position: unset !important;
-    width: 100% !important;
-    height: 100% !important;
-    transform: translate(0px, 0px) !important;
-  }
-
-  .panel {
-    margin: 0 !important;
+    &--fullscreen {
+      display: block !important;
+      position: unset !important;
+      width: 100% !important;
+      height: 100% !important;
+      transform: translate(0px, 0px) !important;
+    }
   }
 
   // Disable grid interaction indicators in fullscreen panels
@@ -61,6 +57,13 @@
   }
 }
 
+.react-grid-item.react-grid-placeholder {
+  box-shadow: $panel-grid-placeholder-shadow;
+  background: $panel-grid-placeholder-bg;
+  z-index: 0;
+  opacity: unset;
+}
+
 .theme-dark {
   .react-grid-item > .react-resizable-handle::after {
     border-right: 2px solid $gray-1;
@@ -83,10 +86,10 @@
 }
 
 // Disable animation on initial rendering and enable it when component has been mounted.
-.react-grid-item.cssTransforms.panel {
+.react-grid-item.cssTransforms {
   transition-property: none;
 }
 
-.animated .react-grid-item.cssTransforms.panel {
+.animated .react-grid-item.cssTransforms {
   transition-property: transform;
 }

+ 1 - 0
public/sass/components/_dashboard_list.scss

@@ -12,6 +12,7 @@
   height: 35px;
   display: flex;
   justify-content: space-between;
+  align-items: center;
 
   .gf-form-button-row {
     padding-top: 0;

+ 1 - 13
public/sass/components/_gf-form.scss

@@ -141,19 +141,6 @@ $input-border: 1px solid $input-border-color;
   @include border-radius($label-border-radius-sm);
 }
 
-.gf-form-checkbox {
-  flex-shrink: 0;
-  padding: $input-padding-y $input-padding-x;
-  line-height: $input-line-height;
-
-  .checkbox-label {
-    display: inline;
-    cursor: pointer;
-    padding: $input-padding-y 0.4rem;
-    line-height: $input-line-height;
-  }
-}
-
 .gf-form-textarea {
   max-width: 650px;
 }
@@ -180,6 +167,7 @@ $input-border: 1px solid $input-border-color;
   @at-root textarea#{&} {
     overflow: auto;
     white-space: pre-wrap;
+    height: auto;
   }
 
   // Unstyle the caret on `<select>`s in IE10+.

+ 4 - 0
public/sass/components/_navbar.scss

@@ -50,6 +50,10 @@
   .navbar-page-btn .fa-caret-down {
     display: none;
   }
+
+  .navbar-buttons--close {
+    display: flex;
+  }
 }
 
 .navbar-page-btn {

+ 144 - 28
public/sass/components/_panel_editor.scss

@@ -4,27 +4,73 @@
   height: 100%;
 }
 
-.panel-editor-container__panel {
-  flex: 1 1 0;
+.panel-wrapper {
+  height: 100%;
+
+  &--edit {
+    flex: 1 1 0;
+    height: unset;
+    margin: 0 $dashboard-padding;
+  }
+
+  &--view {
+    flex: 1 1 0;
+    height: 80%;
+    margin: 0 $dashboard-padding;
+    padding-top: $dashboard-padding;
+  }
 }
 
 .panel-editor-container__editor {
   margin-top: $panel-margin*2;
   display: flex;
-  flex-direction: column;
-  height: 65%;
+  flex-direction: row;
+  height: 60%;
   position: relative;
 }
 
+.panel-editor__right {
+  display: flex;
+  flex-direction: column;
+  flex-grow: 1;
+  background: $page-bg;
+  margin: 0 20px 0 84px;
+  border-left: 2px solid $orange;
+  border-radius: 3px;
+  box-shadow: $panel-editor-shadow;
+}
+
+.panel-editor__close {
+  @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl);
+  position: absolute;
+  left: 11px;
+  top: 5px;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+
+  i {
+    flex-grow: 1;
+    text-align: center;
+    font-size: 20px;
+  }
+}
+
 .panel-editor__scroll {
   flex-grow: 1;
   min-width: 0;
   display: flex;
-  padding: 0 5px;
 }
 
 .panel-editor__content {
-  padding: 40px 15px;
+  padding: 40px 20px;
+}
+
+.panel-editor__toolbar-view {
+  background: $panel-editor-toolbar-view-bg;
+  padding: 20px;
 }
 
 .panel-in-fullscreen {
@@ -94,11 +140,10 @@
 }
 
 .viz-picker__item {
-  background: $card-background;
-  box-shadow: $card-shadow;
-
+  background: $panel-editor-viz-item-bg;
+  border: $panel-editor-viz-item-border;
   border-radius: 3px;
-  height: 90px;
+  height: 100px;
   width: 150px;
   flex-shrink: 0;
   flex-direction: column;
@@ -107,15 +152,27 @@
   display: flex;
   margin-right: 10px;
   margin-bottom: 10px;
-  border: 1px solid transparent;
+  //border: 1px solid transparent;
   align-items: center;
+  justify-content: center;
+  padding-bottom: 6px;
+  transition: transform 1 ease;
 
   &:hover {
-    background: $card-background-hover;
+    box-shadow: $panel-editor-viz-item-shadow-hover;
+    background: $panel-editor-viz-item-bg-hover;
+    border: $panel-editor-viz-item-border-hover;
   }
 
   &--selected {
-    box-shadow: 0 0 12px #ff4d00;
+    box-shadow: 0 0 6px $orange;
+    border: 1px solid $orange;
+
+    &:hover {
+      box-shadow: 0 0 6px $orange;
+      border: 1px solid $orange;
+      background: $panel-editor-viz-item-bg-hover-active;
+    }
   }
 }
 
@@ -128,6 +185,7 @@
   flex-direction: column;
   align-self: center;
   height: 23px;
+  font-weight: 500;
 }
 
 .viz-picker__item-img {
@@ -137,22 +195,55 @@
 .panel-editor-tabs {
   position: relative;
   z-index: 2;
-  box-shadow: $page-header-shadow;
-  border-bottom: 1px solid $page-header-border-color;
-  padding: 0 $dashboard-padding;
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  top: 44px;
+  left: 20px;
+  align-items: flex-start;
+
+  &::before {
+    content: '';
+    display: block;
+    position: absolute;
+    top: 10px;
+    bottom: 10px;
+    left: 21px;
+    width: 2px;
+    background: $panel-editor-tabs-line-color;
+  }
+}
 
-  @include clearfix();
+.panel-editor-tabs__item {
+  margin-bottom: 25px;
+  position: relative;
+  z-index: 1;
+  text-align: center;
 
-  .active.gf-tabs-link {
-    background: $toolbar-tab-bg;
+  &:last-child {
+    margin-bottom: 0;
   }
 }
 
-.panel-editor-tabs__close {
-  padding: 5px 9px;
-  border-radius: $border-radius;
-  float: right;
-  @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
+.panel-editor-tabs__link {
+  display: inline-block;
+
+  &.active {
+    position: relative;
+  }
+
+  .gicon {
+    height: 44px;
+    width: 53px;
+    transition: transform 0.1s ease;
+    margin-right: 5px;
+
+    &:hover {
+      filter: $panel-editor-side-menu-shadow;
+      transform: translate(-2px, -2px);
+      transform: scale(1.1);
+    }
+  }
 }
 
 .ds-picker-list {
@@ -163,21 +254,26 @@
 }
 
 .ds-picker-list__item {
-  background: $card-background;
-  box-shadow: $card-shadow;
-
+  background: $panel-editor-viz-item-bg;
+  border: $panel-editor-viz-item-border;
   border-radius: 3px;
   display: flex;
   cursor: pointer;
   margin-bottom: 3px;
   padding: 5px 15px;
   align-items: center;
+  height: 44px;
 
   &:hover {
-    background: $card-background-hover;
+    background: $panel-editor-viz-item-bg-hover;
+    border: $panel-editor-viz-item-border-hover;
+    box-shadow: $panel-editor-viz-item-shadow-hover;
   }
 
   &--selected {
+    box-shadow: 0 0 6px $orange;
+    border: 1px solid $orange;
+
     .ds-picker-list__name {
       color: $text-color;
     }
@@ -206,3 +302,23 @@
   font-size: $font-size-md;
   margin-bottom: 20px;
 }
+
+.form-section {
+  margin-bottom: 10px;
+}
+
+.form-section__header {
+  padding: 5px 10px;
+  font-size: $font-size-h5;
+  margin-bottom: 20px;
+  background: $input-label-bg;
+  border-radius: 3px;
+}
+
+.form-section__body {
+  padding: 0 10px;
+}
+
+.panel-editor-tabs__item-popover {
+  background: $orange;
+}

+ 7 - 18
public/sass/components/_switch.scss

@@ -78,6 +78,9 @@ input:checked + .gf-form-switch__slider::before {
   border: 1px solid $input-border-color;
   border-left: none;
   border-radius: $input-border-radius;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
   input {
     opacity: 0;
@@ -88,25 +91,14 @@ input:checked + .gf-form-switch__slider::before {
   &--transparent {
     background: transparent;
     border: none;
-  }
-
-  &--search-result__section {
-    width: 3.05rem;
+    width: 20px;
     height: auto;
     position: relative;
-    top: -5px;
-    left: 3px;
-  }
-
-  &--search-result__item {
-    width: 2.7rem;
-    height: auto;
-    position: relative;
-    top: 3px;
+    padding-left: 7px;
   }
 
   &--table-cell {
-    width: 40px;
+    width: 20px;
     background: transparent;
     height: auto;
     border: none;
@@ -116,17 +108,14 @@ input:checked + .gf-form-switch__slider::before {
 }
 
 .gf-form-switch__checkbox {
-  position: absolute;
-  left: 16px;
   height: 16px;
   width: 16px;
   border-radius: 3px;
   border: $checkbox-border;
   background: $checkbox-bg;
-  top: 8px;
   display: flex;
-  justify-content: center;
   align-items: center;
+  justify-content: center;
 }
 
 input:checked + .gf-form-switch__checkbox::before {

+ 10 - 12
public/sass/components/_toolbar.scss

@@ -2,12 +2,18 @@
   display: flex;
   align-content: center;
   align-items: center;
-  background: $toolbar-bg;
-  box-shadow: $toolbar-shadow;
-  padding: 7px 20px 7px 20px;
+  padding: 3px 20px 3px 20px;
   position: relative;
   z-index: 1;
   flex: 0 0 auto;
+  background: $toolbar-bg;
+  border-radius: 3px;
+  height: 44px;
+}
+
+.toolbar__heading {
+  font-size: $font-size-lg;
+  padding-right: 20px;
 }
 
 .toolbar__main {
@@ -36,20 +42,12 @@
   height: 20px;
 }
 
-.toolbar-subview {
-  position: relative;
-  padding: 20px 20px;
-  background-color: $empty-list-cta-bg;
-  top: -45px;
-  margin: 0 30px 20px 0px;
-}
-
 .toolbar-subview__close {
   background: transparent;
   padding: 4px 8px 4px 9px;
   border: none;
   position: absolute;
-  right: 15px;
+  right: 25px;
   top: 20px;
   font-size: $font-size-md;
 

+ 6 - 0
public/sass/layout/_page.scss

@@ -14,6 +14,12 @@
   background: $page-gradient;
 }
 
+.panel-in-fullscreen {
+  .main-view {
+    background: $edit-gradient;
+  }
+}
+
 .page-container {
   margin-left: auto;
   margin-right: auto;

+ 1 - 0
public/sass/pages/_dashboard.scss

@@ -25,6 +25,7 @@ div.flot-text {
     bottom: 0;
     right: 0;
     margin: 0;
+
     .panel-container {
       border: none;
       z-index: $zindex-sidemenu + 1;

+ 7 - 0
public/sass/utils/_utils.scss

@@ -82,3 +82,10 @@ button.close {
 .absolute {
   position: absolute;
 }
+
+.center-vh {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  justify-items: center;
+}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.