Przeglądaj źródła

Merge branch 'pane-edit-ux-tabs' into develop

Torkel Ödegaard 7 lat temu
rodzic
commit
727efa5a03
34 zmienionych plików z 847 dodań i 307 usunięć
  1. 37 0
      public/app/core/components/Animations/FadeIn.tsx
  2. 1 1
      public/app/core/components/Animations/SlideDown.tsx
  3. 1 1
      public/app/core/components/CustomScrollbar/CustomScrollbar.tsx
  4. 6 3
      public/app/core/directives/dash_class.ts
  5. 1 0
      public/app/core/reducers/location.ts
  6. 7 1
      public/app/features/dashboard/dashgrid/DashboardGrid.tsx
  7. 33 34
      public/app/features/dashboard/dashgrid/DashboardPanel.tsx
  8. 88 0
      public/app/features/dashboard/dashgrid/DataSourcePicker.tsx
  9. 96 0
      public/app/features/dashboard/dashgrid/EditorTabBody.tsx
  10. 30 48
      public/app/features/dashboard/dashgrid/PanelEditor.tsx
  11. 24 4
      public/app/features/dashboard/dashgrid/QueriesTab.tsx
  12. 57 0
      public/app/features/dashboard/dashgrid/VisualizationTab.tsx
  13. 42 19
      public/app/features/dashboard/dashgrid/VizTypePicker.tsx
  14. 2 2
      public/app/features/panel/panel_directive.ts
  15. 3 6
      public/app/features/plugins/datasource_srv.ts
  16. 20 5
      public/app/plugins/panel/graph2/module.tsx
  17. 7 0
      public/app/types/datasources.ts
  18. 2 1
      public/app/types/index.ts
  19. 2 1
      public/sass/_grafana.scss
  20. 8 2
      public/sass/_variables.dark.scss
  21. 6 0
      public/sass/_variables.light.scss
  22. 1 1
      public/sass/components/_buttons.scss
  23. 5 1
      public/sass/components/_dashboard_grid.scss
  24. 21 1
      public/sass/components/_gf-form.scss
  25. 2 6
      public/sass/components/_navbar.scss
  26. 208 0
      public/sass/components/_panel_editor.scss
  27. 71 71
      public/sass/components/_scrollbar.scss
  28. 1 2
      public/sass/components/_submenu.scss
  29. 4 2
      public/sass/components/_tabbed_view.scss
  30. 1 0
      public/sass/components/_tabs.scss
  31. 1 0
      public/sass/components/_timepicker.scss
  32. 59 0
      public/sass/components/_toolbar.scss
  33. 0 81
      public/sass/components/_viz_editor.scss
  34. 0 14
      public/sass/pages/_dashboard.scss

+ 37 - 0
public/app/core/components/Animations/FadeIn.tsx

@@ -0,0 +1,37 @@
+import React, { SFC } from 'react';
+import Transition from 'react-transition-group/Transition';
+
+interface Props {
+  duration: number;
+  children: JSX.Element;
+  in: boolean;
+}
+
+export const FadeIn: SFC<Props> = props => {
+  const defaultStyle = {
+    transition: `opacity ${props.duration}ms linear`,
+    opacity: 0,
+  };
+
+  const transitionStyles = {
+    exited: { opacity: 0, display: 'none' },
+    entering: { opacity: 0 },
+    entered: { opacity: 1 },
+    exiting: { opacity: 0 },
+  };
+
+  return (
+    <Transition in={props.in} timeout={props.duration}>
+      {state => (
+        <div
+          style={{
+            ...defaultStyle,
+            ...transitionStyles[state],
+          }}
+        >
+          {props.children}
+        </div>
+      )}
+    </Transition>
+  );
+};

+ 1 - 1
public/app/core/components/Animations/SlideDown.tsx

@@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
   const transitionStyles = {
   const transitionStyles = {
     exited: { maxHeight: 0 },
     exited: { maxHeight: 0 },
     entering: { maxHeight: maxHeight },
     entering: { maxHeight: maxHeight },
-    entered: { maxHeight: maxHeight, overflow: 'visible' },
+    entered: { maxHeight: 'unset', overflow: 'visible' },
     exiting: { maxHeight: 0 },
     exiting: { maxHeight: 0 },
   };
   };
 
 

+ 1 - 1
public/app/core/components/CustomScrollbar/CustomScrollbar.tsx

@@ -15,7 +15,7 @@ interface Props {
 class CustomScrollbar extends PureComponent<Props> {
 class CustomScrollbar extends PureComponent<Props> {
   static defaultProps: Partial<Props> = {
   static defaultProps: Partial<Props> = {
     customClassName: 'custom-scrollbars',
     customClassName: 'custom-scrollbars',
-    autoHide: true,
+    autoHide: false,
     autoHideTimeout: 200,
     autoHideTimeout: 200,
     autoHideDuration: 200,
     autoHideDuration: 200,
     hideTracksWhenNotNeeded: false,
     hideTracksWhenNotNeeded: false,

+ 6 - 3
public/app/core/directives/dash_class.ts

@@ -1,3 +1,4 @@
+import $ from 'jquery';
 import _ from 'lodash';
 import _ from 'lodash';
 import coreModule from '../core_module';
 import coreModule from '../core_module';
 
 
@@ -5,18 +6,20 @@ import coreModule from '../core_module';
 function dashClass($timeout) {
 function dashClass($timeout) {
   return {
   return {
     link: ($scope, elem) => {
     link: ($scope, elem) => {
+      const body = $('body');
+
       $scope.ctrl.dashboard.events.on('view-mode-changed', panel => {
       $scope.ctrl.dashboard.events.on('view-mode-changed', panel => {
         console.log('view-mode-changed', panel.fullscreen);
         console.log('view-mode-changed', panel.fullscreen);
         if (panel.fullscreen) {
         if (panel.fullscreen) {
-          elem.addClass('panel-in-fullscreen');
+          body.addClass('panel-in-fullscreen');
         } else {
         } else {
           $timeout(() => {
           $timeout(() => {
-            elem.removeClass('panel-in-fullscreen');
+            body.removeClass('panel-in-fullscreen');
           });
           });
         }
         }
       });
       });
 
 
-      elem.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true);
+      body.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true);
 
 
       $scope.$watch('ctrl.dashboardViewState.state.editview', newValue => {
       $scope.$watch('ctrl.dashboardViewState.state.editview', newValue => {
         if (newValue) {
         if (newValue) {

+ 1 - 0
public/app/core/reducers/location.ts

@@ -18,6 +18,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS
 
 
       if (action.payload.partial) {
       if (action.payload.partial) {
         query = _.defaults(query, state.query);
         query = _.defaults(query, state.query);
+        query = _.omitBy(query, _.isNull);
       }
       }
 
 
       return {
       return {

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

@@ -8,6 +8,7 @@ import classNames from 'classnames';
 import sizeMe from 'react-sizeme';
 import sizeMe from 'react-sizeme';
 
 
 let lastGridWidth = 1200;
 let lastGridWidth = 1200;
+let ignoreNextWidthChange = false;
 
 
 function GridWrapper({
 function GridWrapper({
   size,
   size,
@@ -24,8 +25,12 @@ function GridWrapper({
   isFullscreen,
   isFullscreen,
 }) {
 }) {
   const width = size.width > 0 ? size.width : lastGridWidth;
   const width = size.width > 0 ? size.width : lastGridWidth;
+
+  // logic to ignore width changes (optimization)
   if (width !== lastGridWidth) {
   if (width !== lastGridWidth) {
-    if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
+    if (ignoreNextWidthChange) {
+      ignoreNextWidthChange = false;
+    } else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
       onWidthChange();
       onWidthChange();
       lastGridWidth = width;
       lastGridWidth = width;
     }
     }
@@ -138,6 +143,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
   }
   }
 
 
   onViewModeChanged(payload) {
   onViewModeChanged(payload) {
+    ignoreNextWidthChange = true;
     this.setState({ animated: !payload.fullscreen });
     this.setState({ animated: !payload.fullscreen });
   }
   }
 
 

+ 33 - 34
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -1,15 +1,18 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import config from 'app/core/config';
 import config from 'app/core/config';
-import { PanelModel } from '../panel_model';
-import { DashboardModel } from '../dashboard_model';
+
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
-import { DashboardRow } from './DashboardRow';
-import { AddPanelPanel } from './AddPanelPanel';
 import { importPluginModule } from 'app/features/plugins/plugin_loader';
 import { importPluginModule } from 'app/features/plugins/plugin_loader';
-import { PluginExports, PanelPlugin } from 'app/types/plugins';
+
+import { AddPanelPanel } from './AddPanelPanel';
+import { DashboardRow } from './DashboardRow';
+import { PanelPlugin } from 'app/types/plugins';
 import { PanelChrome } from './PanelChrome';
 import { PanelChrome } from './PanelChrome';
 import { PanelEditor } from './PanelEditor';
 import { PanelEditor } from './PanelEditor';
 
 
+import { PanelModel } from '../panel_model';
+import { DashboardModel } from '../dashboard_model';
+
 export interface Props {
 export interface Props {
   panel: PanelModel;
   panel: PanelModel;
   dashboard: DashboardModel;
   dashboard: DashboardModel;
@@ -18,20 +21,19 @@ export interface Props {
 }
 }
 
 
 export interface State {
 export interface State {
-  pluginExports: PluginExports;
+  plugin: PanelPlugin;
 }
 }
 
 
 export class DashboardPanel extends PureComponent<Props, State> {
 export class DashboardPanel extends PureComponent<Props, State> {
   element: any;
   element: any;
   angularPanel: AngularComponent;
   angularPanel: AngularComponent;
-  pluginInfo: any;
   specialPanels = {};
   specialPanels = {};
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
     this.state = {
     this.state = {
-      pluginExports: null,
+      plugin: null,
     };
     };
 
 
     this.specialPanels['row'] = this.renderRow.bind(this);
     this.specialPanels['row'] = this.renderRow.bind(this);
@@ -64,20 +66,22 @@ export class DashboardPanel extends PureComponent<Props, State> {
       return;
       return;
     }
     }
 
 
+    const { panel } = this.props;
+
     // handle plugin loading & changing of plugin type
     // handle plugin loading & changing of plugin type
-    if (!this.pluginInfo || this.pluginInfo.id !== this.props.panel.type) {
-      this.pluginInfo = config.panels[this.props.panel.type];
+    if (!this.state.plugin || this.state.plugin.id !== panel.type) {
+      const plugin = config.panels[panel.type];
 
 
-      if (this.pluginInfo.exports) {
+      if (plugin.exports) {
         this.cleanUpAngularPanel();
         this.cleanUpAngularPanel();
-        this.setState({ pluginExports: this.pluginInfo.exports });
+        this.setState({ plugin: plugin });
       } else {
       } else {
-        importPluginModule(this.pluginInfo.module).then(pluginExports => {
+        importPluginModule(plugin.module).then(pluginExports => {
           this.cleanUpAngularPanel();
           this.cleanUpAngularPanel();
           // cache plugin exports (saves a promise async cycle next time)
           // cache plugin exports (saves a promise async cycle next time)
-          this.pluginInfo.exports = pluginExports;
+          plugin.exports = pluginExports;
           // update panel state
           // update panel state
-          this.setState({ pluginExports: pluginExports });
+          this.setState({ plugin: plugin });
         });
         });
       }
       }
     }
     }
@@ -113,7 +117,9 @@ export class DashboardPanel extends PureComponent<Props, State> {
   }
   }
 
 
   renderReactPanel() {
   renderReactPanel() {
-    const { pluginExports } = this.state;
+    const { dashboard, panel } = this.props;
+    const { plugin } = this.state;
+
     const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
     const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
     const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : '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
     // this might look strange with these classes that change when edit, but
@@ -121,37 +127,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
     return (
     return (
       <div className={containerClass}>
       <div className={containerClass}>
         <div className={panelWrapperClass}>
         <div className={panelWrapperClass}>
-          <PanelChrome
-            component={pluginExports.PanelComponent}
-            panel={this.props.panel}
-            dashboard={this.props.dashboard}
-          />
+          <PanelChrome component={plugin.exports.PanelComponent} panel={panel} dashboard={dashboard} />
         </div>
         </div>
-        {this.props.panel.isEditing && (
-          <div className="panel-editor-container__editor">
-            <PanelEditor
-              panel={this.props.panel}
-              panelType={this.props.panel.type}
-              dashboard={this.props.dashboard}
-              onTypeChanged={this.onPluginTypeChanged}
-              pluginExports={pluginExports}
-            />
-          </div>
+        {panel.isEditing && (
+          <PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
         )}
         )}
       </div>
       </div>
     );
     );
   }
   }
 
 
   render() {
   render() {
+    const { panel } = this.props;
+    const { plugin } = this.state;
+
     if (this.isSpecial()) {
     if (this.isSpecial()) {
-      return this.specialPanels[this.props.panel.type]();
+      return this.specialPanels[panel.type]();
     }
     }
 
 
-    if (!this.state.pluginExports) {
+    // if we have not loaded plugin exports yet, wait
+    if (!plugin || !plugin.exports) {
       return null;
       return null;
     }
     }
 
 
-    if (this.state.pluginExports.PanelComponent) {
+    // if exporting PanelComponent it must be a react panel
+    if (plugin.exports.PanelComponent) {
       return this.renderReactPanel();
       return this.renderReactPanel();
     }
     }
 
 

+ 88 - 0
public/app/features/dashboard/dashgrid/DataSourcePicker.tsx

@@ -0,0 +1,88 @@
+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 State {
+  datasources: DataSourceSelectItem[];
+  searchQuery: string;
+}
+
+export class DataSourcePicker extends PureComponent<Props, State> {
+  searchInput: HTMLElement;
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      datasources: getDatasourceSrv().getMetricSources(),
+      searchQuery: '',
+    };
+  }
+
+  getDataSources() {
+    const { datasources, searchQuery } = this.state;
+    const regex = new RegExp(searchQuery, 'i');
+
+    const filtered = datasources.filter(item => {
+      return regex.test(item.name) || regex.test(item.meta.name);
+    });
+
+    return _.sortBy(filtered, 'sort');
+  }
+
+  renderDataSource = (ds: DataSourceSelectItem, index) => {
+    const cssClass = classNames({
+      'ds-picker-list__item': true,
+    });
+
+    return (
+      <div key={index} className={cssClass} title={ds.name}>
+        <img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
+        <div className="ds-picker-list__name">{ds.name}</div>
+      </div>
+    );
+  };
+
+  componentDidMount() {
+    setTimeout(() => {
+      this.searchInput.focus();
+    }, 300);
+  }
+
+  renderFilters() {
+    return (
+      <>
+        <label className="gf-form--has-input-icon">
+          <input
+            type="text"
+            className="gf-form-input width-13"
+            placeholder=""
+            ref={elem => (this.searchInput = elem)}
+          />
+          <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>
+      </>
+    );
+  }
+
+  render() {
+    return (
+      <>
+        <div className="cta-form__bar">
+          {this.renderFilters()}
+          <div className="gf-form--grow" />
+        </div>
+        <div className="ds-picker-list">{this.getDataSources().map(this.renderDataSource)}</div>
+      </>
+    );
+  }
+}

+ 96 - 0
public/app/features/dashboard/dashgrid/EditorTabBody.tsx

@@ -0,0 +1,96 @@
+import React, { PureComponent } from 'react';
+import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
+import { FadeIn } from 'app/core/components/Animations/FadeIn';
+
+interface Props {
+  children: JSX.Element;
+  main: EditorToolBarView;
+  toolbarItems: EditorToolBarView[];
+}
+
+export interface EditorToolBarView {
+  title: string;
+  imgSrc?: string;
+  icon?: string;
+  render: () => JSX.Element;
+}
+
+interface State {
+  openView?: EditorToolBarView;
+}
+
+export class EditorTabBody extends PureComponent<Props, State> {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      openView: null,
+    };
+  }
+
+  onToggleToolBarView = (item: EditorToolBarView) => {
+    this.setState({
+      openView: item === this.state.openView ? null : item,
+    });
+  };
+
+  onCloseOpenView = () => {
+    this.setState({ openView: null });
+  };
+
+  renderMainSelection(view: EditorToolBarView) {
+    return (
+      <div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title}>
+        <img className="toolbar__main-image" src={view.imgSrc} />
+        <div className="toolbar__main-name">{view.title}</div>
+        <i className="fa fa-caret-down" />
+      </div>
+    );
+  }
+
+  renderButton(view: EditorToolBarView) {
+    return (
+      <div className="nav-buttons" key={view.title}>
+        <button className="btn navbar-button" onClick={() => this.onToggleToolBarView(view)}>
+          {view.icon && <i className={view.icon} />} {view.title}
+        </button>
+      </div>
+    );
+  }
+
+  renderOpenView(view: EditorToolBarView) {
+    return (
+      <div className="toolbar-subview">
+        <button className="toolbar-subview__close" onClick={this.onCloseOpenView}>
+          <i className="fa fa-chevron-up" />
+        </button>
+        {view.render()}
+      </div>
+    );
+  }
+
+  render() {
+    const { children, toolbarItems, main } = this.props;
+    const { openView } = this.state;
+
+    return (
+      <>
+        <div className="toolbar">
+          {this.renderMainSelection(main)}
+          <div className="gf-form--grow" />
+          {toolbarItems.map(item => this.renderButton(item))}
+        </div>
+        <div className="panel-editor__scroll">
+          <CustomScrollbar>
+            <div className="panel-editor__content">
+              <FadeIn in={openView !== null} duration={200}>
+                {openView && this.renderOpenView(openView)}
+              </FadeIn>
+              {children}
+            </div>
+          </CustomScrollbar>
+        </div>
+      </>
+    );
+  }
+}

+ 30 - 48
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -2,20 +2,19 @@ import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import classNames from 'classnames';
 
 
 import { QueriesTab } from './QueriesTab';
 import { QueriesTab } from './QueriesTab';
-import { VizTypePicker } from './VizTypePicker';
+import { VisualizationTab } from './VisualizationTab';
 
 
 import { store } from 'app/store/store';
 import { store } from 'app/store/store';
 import { updateLocation } from 'app/core/actions';
 import { updateLocation } from 'app/core/actions';
 
 
 import { PanelModel } from '../panel_model';
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { DashboardModel } from '../dashboard_model';
-import { PanelPlugin, PluginExports } from 'app/types/plugins';
+import { PanelPlugin } from 'app/types/plugins';
 
 
 interface PanelEditorProps {
 interface PanelEditorProps {
   panel: PanelModel;
   panel: PanelModel;
   dashboard: DashboardModel;
   dashboard: DashboardModel;
-  panelType: string;
-  pluginExports: PluginExports;
+  plugin: PanelPlugin;
   onTypeChanged: (newType: PanelPlugin) => void;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 }
 
 
@@ -34,43 +33,10 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
     this.tabs = [
     this.tabs = [
       { id: 'queries', text: 'Queries', icon: 'fa fa-database' },
       { id: 'queries', text: 'Queries', icon: 'fa fa-database' },
       { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' },
       { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' },
+      { id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' },
     ];
     ];
   }
   }
 
 
-  renderQueriesTab() {
-    return <QueriesTab panel={this.props.panel} dashboard={this.props.dashboard} />;
-  }
-
-  renderPanelOptions() {
-    const { pluginExports, panel } = this.props;
-
-    if (pluginExports.PanelOptionsComponent) {
-      const OptionsComponent = pluginExports.PanelOptionsComponent;
-      return <OptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
-    } else {
-      return <p>Visualization has no options</p>;
-    }
-  }
-
-  onPanelOptionsChanged = (options: any) => {
-    this.props.panel.updateOptions(options);
-    this.forceUpdate();
-  };
-
-  renderVizTab() {
-    return (
-      <div className="viz-editor">
-        <div className="viz-editor-col1">
-          <VizTypePicker currentType={this.props.panel.type} onTypeChanged={this.props.onTypeChanged} />
-        </div>
-        <div className="viz-editor-col2">
-          <h5 className="page-heading">Options</h5>
-          {this.renderPanelOptions()}
-        </div>
-      </div>
-    );
-  }
-
   onChangeTab = (tab: PanelEditorTab) => {
   onChangeTab = (tab: PanelEditorTab) => {
     store.dispatch(
     store.dispatch(
       updateLocation({
       updateLocation({
@@ -81,28 +47,44 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
     this.forceUpdate();
     this.forceUpdate();
   };
   };
 
 
+  onClose = () => {
+    store.dispatch(
+      updateLocation({
+        query: { tab: null, fullscreen: null, edit: null },
+        partial: true,
+      })
+    );
+  };
+
   render() {
   render() {
+    const { panel, dashboard, onTypeChanged, plugin } = this.props;
     const { location } = store.getState();
     const { location } = store.getState();
     const activeTab = location.query.tab || 'queries';
     const activeTab = location.query.tab || 'queries';
 
 
     return (
     return (
-      <div className="tabbed-view tabbed-view--new">
-        <div className="tabbed-view-header">
+      <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-tabs">
           <ul className="gf-tabs">
           <ul className="gf-tabs">
             {this.tabs.map(tab => {
             {this.tabs.map(tab => {
               return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
               return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
             })}
             })}
           </ul>
           </ul>
 
 
-          <button className="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
-            <i className="fa fa-remove" />
+          <button className="panel-editor-tabs__close" onClick={this.onClose}>
+            <i className="fa fa-reply" />
           </button>
           </button>
         </div>
         </div>
 
 
-        <div className="tabbed-view-body">
-          {activeTab === 'queries' && this.renderQueriesTab()}
-          {activeTab === 'visualization' && this.renderVizTab()}
-        </div>
+        {activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
+        {activeTab === 'visualization' && (
+          <VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
+        )}
       </div>
       </div>
     );
     );
   }
   }
@@ -121,8 +103,8 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) {
   });
   });
 
 
   return (
   return (
-    <li className="gf-tabs-item" key={tab.id}>
-      <a className={tabClasses} onClick={() => onClick(tab)}>
+    <li className="gf-tabs-item" onClick={() => onClick(tab)}>
+      <a className={tabClasses}>
         <i className={tab.icon} /> {tab.text}
         <i className={tab.icon} /> {tab.text}
       </a>
       </a>
     </li>
     </li>

+ 24 - 4
public/app/features/dashboard/dashgrid/QueriesTab.tsx

@@ -1,10 +1,9 @@
-// Libraries
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 
 
-// Services & utils
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
+import { EditorTabBody } from './EditorTabBody';
+import { DataSourcePicker } from './DataSourcePicker';
 
 
-// Types
 import { PanelModel } from '../panel_model';
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { DashboardModel } from '../dashboard_model';
 
 
@@ -48,6 +47,27 @@ export class QueriesTab extends PureComponent<Props> {
   }
   }
 
 
   render() {
   render() {
-    return <div ref={element => (this.element = element)} className="panel-height-helper" />;
+    const currentDataSource = {
+      title: 'ProductionDB',
+      imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
+      render: () => <DataSourcePicker />,
+    };
+
+    const queryInspector = {
+      title: 'Query Inspector',
+      render: () => <h2>hello</h2>,
+    };
+
+    const dsHelp = {
+      title: '',
+      icon: 'fa fa-question',
+      render: () => <h2>hello</h2>,
+    };
+
+    return (
+      <EditorTabBody main={currentDataSource} toolbarItems={[queryInspector, dsHelp]}>
+        <div ref={element => (this.element = element)} style={{ width: '100%' }} />
+      </EditorTabBody>
+    );
   }
   }
 }
 }

+ 57 - 0
public/app/features/dashboard/dashgrid/VisualizationTab.tsx

@@ -0,0 +1,57 @@
+import React, { PureComponent } from 'react';
+
+import { EditorTabBody } from './EditorTabBody';
+import { VizTypePicker } from './VizTypePicker';
+
+import { PanelModel } from '../panel_model';
+import { DashboardModel } from '../dashboard_model';
+import { PanelPlugin } from 'app/types/plugins';
+
+interface Props {
+  panel: PanelModel;
+  dashboard: DashboardModel;
+  plugin: PanelPlugin;
+  onTypeChanged: (newType: PanelPlugin) => void;
+}
+
+export class VisualizationTab extends PureComponent<Props> {
+  constructor(props) {
+    super(props);
+  }
+
+  renderPanelOptions() {
+    const { plugin, panel } = this.props;
+    const { PanelOptionsComponent } = plugin.exports;
+
+    if (PanelOptionsComponent) {
+      return <PanelOptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
+    } else {
+      return <p>Visualization has no options</p>;
+    }
+  }
+
+  onPanelOptionsChanged = (options: any) => {
+    this.props.panel.updateOptions(options);
+    this.forceUpdate();
+  };
+
+  render() {
+    const { plugin } = this.props;
+
+    const panelSelection = {
+      title: plugin.name,
+      imgSrc: plugin.info.logos.small,
+      render: () => {
+        // the needs to be scoped inside this closure
+        const { plugin, onTypeChanged } = this.props;
+        return <VizTypePicker current={plugin} onTypeChanged={onTypeChanged} />;
+      },
+    };
+
+    return (
+      <EditorTabBody main={panelSelection} toolbarItems={[]}>
+        {this.renderPanelOptions()}
+      </EditorTabBody>
+    );
+  }
+}

+ 42 - 19
public/app/features/dashboard/dashgrid/VizTypePicker.tsx

@@ -1,12 +1,12 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import classNames from 'classnames';
+import _ from 'lodash';
+
 import config from 'app/core/config';
 import config from 'app/core/config';
 import { PanelPlugin } from 'app/types/plugins';
 import { PanelPlugin } from 'app/types/plugins';
-import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
-import _ from 'lodash';
 
 
 interface Props {
 interface Props {
-  currentType: string;
+  current: PanelPlugin;
   onTypeChanged: (newType: PanelPlugin) => void;
   onTypeChanged: (newType: PanelPlugin) => void;
 }
 }
 
 
@@ -15,6 +15,8 @@ interface State {
 }
 }
 
 
 export class VizTypePicker extends PureComponent<Props, State> {
 export class VizTypePicker extends PureComponent<Props, State> {
+  searchInput: HTMLElement;
+
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
@@ -36,34 +38,55 @@ export class VizTypePicker extends PureComponent<Props, State> {
   renderVizPlugin = (plugin, index) => {
   renderVizPlugin = (plugin, index) => {
     const cssClass = classNames({
     const cssClass = classNames({
       'viz-picker__item': true,
       'viz-picker__item': true,
-      'viz-picker__item--selected': plugin.id === this.props.currentType,
+      'viz-picker__item--selected': plugin.id === this.props.current.id,
     });
     });
 
 
     return (
     return (
       <div key={index} className={cssClass} onClick={() => this.props.onTypeChanged(plugin)} title={plugin.name}>
       <div key={index} className={cssClass} onClick={() => this.props.onTypeChanged(plugin)} title={plugin.name}>
-        <img className="viz-picker__item-img" src={plugin.info.logos.small} />
         <div className="viz-picker__item-name">{plugin.name}</div>
         <div className="viz-picker__item-name">{plugin.name}</div>
+        <img className="viz-picker__item-img" src={plugin.info.logos.small} />
       </div>
       </div>
     );
     );
   };
   };
 
 
-  render() {
+  componentDidMount() {
+    setTimeout(() => {
+      this.searchInput.focus();
+    }, 300);
+  }
+
+  renderFilters() {
     return (
     return (
-      <div className="viz-picker">
-        <div className="viz-picker__search">
-          <div className="gf-form gf-form--grow">
-            <label className="gf-form--has-input-icon gf-form--grow">
-              <input type="text" className="gf-form-input" placeholder="Search type" />
-              <i className="gf-form-input-icon fa fa-search" />
-            </label>
-          </div>
+      <>
+        <label className="gf-form--has-input-icon">
+          <input
+            type="text"
+            className="gf-form-input width-13"
+            placeholder=""
+            ref={elem => (this.searchInput = elem)}
+          />
+          <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>
         </div>
-        <div className="viz-picker__items">
-          <CustomScrollbar>
-            <div className="scroll-margin-helper">{this.state.pluginList.map(this.renderVizPlugin)}</div>
-          </CustomScrollbar>
+      </>
+    );
+  }
+
+  render() {
+    const { pluginList } = this.state;
+
+    return (
+      <>
+        <div className="cta-form__bar">
+          {this.renderFilters()}
+          <div className="gf-form--grow" />
         </div>
         </div>
-      </div>
+
+        <div className="viz-picker">{pluginList.map(this.renderVizPlugin)}</div>
+      </>
     );
     );
   }
   }
 }
 }

+ 2 - 2
public/app/features/panel/panel_directive.ts

@@ -44,8 +44,8 @@ const panelTemplate = `
             </li>
             </li>
           </ul>
           </ul>
 
 
-          <button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
-            <i class="fa fa-remove"></i>
+          <button class="panel-editor-tabs__close" ng-click="ctrl.exitFullscreen();">
+            <i class="fa fa-reply"></i>
           </button>
           </button>
         </div>
         </div>
 
 

+ 3 - 6
public/app/features/plugins/datasource_srv.ts

@@ -1,14 +1,11 @@
-// Libraries
 import _ from 'lodash';
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
 import coreModule from 'app/core/core_module';
 
 
-// Utils
 import config from 'app/core/config';
 import config from 'app/core/config';
 import { importPluginModule } from './plugin_loader';
 import { importPluginModule } from './plugin_loader';
 
 
-// Types
 import { DataSourceApi } from 'app/types/series';
 import { DataSourceApi } from 'app/types/series';
-import { DataSource } from 'app/types';
+import { DataSource, DataSourceSelectItem } from 'app/types';
 
 
 export class DatasourceSrv {
 export class DatasourceSrv {
   datasources: { [name: string]: DataSource };
   datasources: { [name: string]: DataSource };
@@ -102,8 +99,8 @@ export class DatasourceSrv {
     return _.sortBy(es, ['name']);
     return _.sortBy(es, ['name']);
   }
   }
 
 
-  getMetricSources(options) {
-    const metricSources = [];
+  getMetricSources(options?) {
+    const metricSources: DataSourceSelectItem[] = [];
 
 
     _.each(config.datasources, (value, key) => {
     _.each(config.datasources, (value, key) => {
       if (value.meta && value.meta.metrics) {
       if (value.meta && value.meta.metrics) {

+ 20 - 5
public/app/plugins/panel/graph2/module.tsx

@@ -61,11 +61,26 @@ export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> {
 
 
     return (
     return (
       <div>
       <div>
-        <div className="section gf-form-group">
-          <h5 className="page-heading">Draw Modes</h5>
-          <Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
-          <Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
-          <Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
+        <div className="form-option-box">
+          <div className="form-option-box__header">Display Options</div>
+          <div className="section gf-form-group">
+            <h5 className="section-heading">Draw Modes</h5>
+            <Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
+            <Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
+            <Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
+          </div>
+          <div className="section gf-form-group">
+            <h5 className="section-heading">Test Options</h5>
+            <Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
+            <Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
+            <Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
+          </div>
+        </div>
+        <div className="form-option-box">
+          <div className="form-option-box__header">Axes</div>
+        </div>
+        <div className="form-option-box">
+          <div className="form-option-box__header">Thresholds</div>
         </div>
         </div>
       </div>
       </div>
     );
     );

+ 7 - 0
public/app/types/datasources.ts

@@ -25,6 +25,13 @@ export interface DataSource {
   testDatasource?: () => Promise<any>;
   testDatasource?: () => Promise<any>;
 }
 }
 
 
+export interface DataSourceSelectItem {
+  name: string;
+  value: string | null;
+  meta: PluginMeta;
+  sort: string;
+}
+
 export interface DataSourcesState {
 export interface DataSourcesState {
   dataSources: DataSource[];
   dataSources: DataSource[];
   searchQuery: string;
   searchQuery: string;

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

@@ -7,7 +7,7 @@ import { DashboardState } from './dashboard';
 import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
 import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
 import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
 import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
 import { Invitee, OrgUser, User, UsersState, UserState } from './user';
 import { Invitee, OrgUser, User, UsersState, UserState } from './user';
-import { DataSource, DataSourcesState } from './datasources';
+import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
 import {
 import {
   TimeRange,
   TimeRange,
   LoadingState,
   LoadingState,
@@ -55,6 +55,7 @@ export {
   OrgRole,
   OrgRole,
   PermissionLevel,
   PermissionLevel,
   DataSource,
   DataSource,
+  DataSourceSelectItem,
   PluginMeta,
   PluginMeta,
   ApiKey,
   ApiKey,
   ApiKeysState,
   ApiKeysState,

+ 2 - 1
public/sass/_grafana.scss

@@ -97,7 +97,8 @@
 @import 'components/form_select_box';
 @import 'components/form_select_box';
 @import 'components/user-picker';
 @import 'components/user-picker';
 @import 'components/description-picker';
 @import 'components/description-picker';
-@import 'components/viz_editor';
+@import 'components/panel_editor';
+@import 'components/toolbar';
 @import 'components/delete_button';
 @import 'components/delete_button';
 @import 'components/add_data_source.scss';
 @import 'components/add_data_source.scss';
 @import 'components/page_loader';
 @import 'components/page_loader';

+ 8 - 2
public/sass/_variables.dark.scss

@@ -77,6 +77,7 @@ $brand-gradient: linear-gradient(
   rgba(255, 68, 0, 0.7) 99%,
   rgba(255, 68, 0, 0.7) 99%,
   rgba(255, 68, 0, 0.7) 100%
   rgba(255, 68, 0, 0.7) 100%
 );
 );
+
 $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
 $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
 
 
 // Links
 // Links
@@ -110,7 +111,6 @@ $divider-border-color: #555;
 
 
 // Graphite Target Editor
 // Graphite Target Editor
 $tight-form-bg: $dark-3;
 $tight-form-bg: $dark-3;
-
 $tight-form-func-bg: #333334;
 $tight-form-func-bg: #333334;
 $tight-form-func-highlight-bg: #444445;
 $tight-form-func-highlight-bg: #444445;
 
 
@@ -128,6 +128,7 @@ $list-item-bg: $card-background;
 $list-item-hover-bg: lighten($gray-blue, 2%);
 $list-item-hover-bg: lighten($gray-blue, 2%);
 $list-item-link-color: $text-color;
 $list-item-link-color: $text-color;
 $list-item-shadow: $card-shadow;
 $list-item-shadow: $card-shadow;
+
 $empty-list-cta-bg: $gray-blue;
 $empty-list-cta-bg: $gray-blue;
 
 
 // Scrollbars
 // Scrollbars
@@ -152,8 +153,8 @@ $table-bg-hover: $dark-3;
 $btn-primary-bg: #ff6600;
 $btn-primary-bg: #ff6600;
 $btn-primary-bg-hl: #bc3e06;
 $btn-primary-bg-hl: #bc3e06;
 
 
-$btn-secondary-bg: $blue-dark;
 $btn-secondary-bg-hl: lighten($blue-dark, 5%);
 $btn-secondary-bg-hl: lighten($blue-dark, 5%);
+$btn-secondary-bg: $blue-dark;
 
 
 $btn-success-bg: $green;
 $btn-success-bg: $green;
 $btn-success-bg-hl: darken($green, 6%);
 $btn-success-bg-hl: darken($green, 6%);
@@ -266,6 +267,11 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black;
 // -------------------------
 // -------------------------
 $tab-border-color: $dark-4;
 $tab-border-color: $dark-4;
 
 
+// Toolbar
+$toolbar-bg: $page-header-bg;
+$toolbar-shadow: 0 0 20px black;
+$toolbar-tab-bg: $gray-blue;
+
 // Pagination
 // Pagination
 // -------------------------
 // -------------------------
 
 

+ 6 - 0
public/sass/_variables.light.scss

@@ -31,6 +31,7 @@ $white: #fff;
 // Accent colors
 // Accent colors
 // -------------------------
 // -------------------------
 $blue: #0083b3;
 $blue: #0083b3;
+$blue-dark: #005f81;
 $blue-light: #00a8e6;
 $blue-light: #00a8e6;
 $green: #3aa655;
 $green: #3aa655;
 $red: #d44939;
 $red: #d44939;
@@ -213,6 +214,11 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
 // -------------------------
 // -------------------------
 $tab-border-color: $gray-5;
 $tab-border-color: $gray-5;
 
 
+// Toolbar
+$toolbar-bg: linear-gradient(90deg, #ffffff, #e6eef9);
+$toolbar-shadow: 1px 1px 3px #c7d0d8;
+$toolbar-tab-bg: $white;
+
 // search
 // search
 $search-shadow: 0 5px 30px 0 $gray-4;
 $search-shadow: 0 5px 30px 0 $gray-4;
 $search-filter-box-bg: $gray-7;
 $search-filter-box-bg: $gray-7;

+ 1 - 1
public/sass/components/_buttons.scss

@@ -120,8 +120,8 @@
 // Info appears as a neutral blue
 // Info appears as a neutral blue
 .btn-secondary {
 .btn-secondary {
   @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl);
   @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl);
+  // Inverse appears as dark gray
 }
 }
-// Inverse appears as dark gray
 .btn-inverse {
 .btn-inverse {
   @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow);
   @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow);
   //background: $card-background;
   //background: $card-background;

+ 5 - 1
public/sass/components/_dashboard_grid.scss

@@ -3,7 +3,7 @@
 
 
 .panel-in-fullscreen {
 .panel-in-fullscreen {
   .react-grid-layout {
   .react-grid-layout {
-    height: 100% !important;
+    height: calc(100% - 20px) !important;
   }
   }
 
 
   .react-grid-item {
   .react-grid-item {
@@ -19,6 +19,10 @@
     transform: translate(0px, 0px) !important;
     transform: translate(0px, 0px) !important;
   }
   }
 
 
+  .panel {
+    margin: 0 !important;
+  }
+
   // Disable grid interaction indicators in fullscreen panels
   // Disable grid interaction indicators in fullscreen panels
   .panel-header:hover {
   .panel-header:hover {
     background-color: inherit;
     background-color: inherit;

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

@@ -415,7 +415,27 @@ select.gf-form-input ~ .gf-form-help-icon {
 }
 }
 
 
 .cta-form__close {
 .cta-form__close {
+  background: transparent;
+  padding: 4px 8px 4px 9px;
+  border: none;
   position: absolute;
   position: absolute;
   right: 0;
   right: 0;
-  top: 0;
+  top: -2px;
+  font-size: $font-size-md;
+
+  &:hover {
+    color: $text-color-strong;
+  }
+}
+
+.cta-form__bar {
+  display: flex;
+  align-items: center;
+  align-content: center;
+  margin-bottom: 20px;
+}
+
+.cta-form__bar-header {
+  font-size: $font-size-h4;
+  padding-right: 20px;
 }
 }

+ 2 - 6
public/sass/components/_navbar.scss

@@ -41,7 +41,7 @@
 
 
 .panel-in-fullscreen {
 .panel-in-fullscreen {
   .navbar {
   .navbar {
-    @include navbar-alt-look();
+    padding-left: 15px;
   }
   }
 
 
   .navbar-button--add-panel,
   .navbar-button--add-panel,
@@ -50,10 +50,6 @@
   .navbar-page-btn .fa-caret-down {
   .navbar-page-btn .fa-caret-down {
     display: none;
     display: none;
   }
   }
-
-  .navbar-buttons--close {
-    display: flex;
-  }
 }
 }
 
 
 .navbar-page-btn {
 .navbar-page-btn {
@@ -98,7 +94,7 @@
 }
 }
 
 
 .navbar-buttons {
 .navbar-buttons {
-  height: $navbarHeight;
+  // height: $navbarHeight;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: flex-end;
   justify-content: flex-end;

+ 208 - 0
public/sass/components/_panel_editor.scss

@@ -0,0 +1,208 @@
+.panel-editor-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.panel-editor-container__panel {
+  flex: 1 1 0;
+}
+
+.panel-editor-container__editor {
+  margin-top: $panel-margin*2;
+  display: flex;
+  flex-direction: column;
+  height: 65%;
+  position: relative;
+}
+
+.panel-editor__scroll {
+  flex-grow: 1;
+  min-width: 0;
+  display: flex;
+  padding: 0 5px;
+}
+
+.panel-editor__content {
+  padding: 40px 15px;
+}
+
+.panel-in-fullscreen {
+  .sidemenu {
+    display: none;
+  }
+
+  .dashboard-container {
+    padding: 0;
+  }
+
+  .submenu-controls {
+    padding: 0 $dashboard-padding $panel-margin $dashboard-padding;
+  }
+
+  .panel-editor-container__panel {
+    margin: 0 $dashboard-padding;
+  }
+}
+
+.panel-editor-resizer {
+  position: absolute;
+  height: 2px;
+  width: 100%;
+  top: -23px;
+  text-align: center;
+  border-bottom: 2px dashed transparent;
+
+  &:hover {
+    transition: border-color 0.2s ease-in 0.4s;
+    transition-delay: 0.2s;
+    border-color: $text-color-faint;
+  }
+}
+
+.panel-editor-resizer__handle {
+  display: inline-block;
+  width: 180px;
+  position: relative;
+  border-radius: 2px;
+  height: 10px;
+  cursor: grabbing;
+  background: $input-label-bg;
+  top: -8px;
+
+  &:hover {
+    transition: background 0.2s ease-in 0.4s;
+    transition-delay: 0.2s;
+    background: linear-gradient(90deg, $orange, $red);
+    .panel-editor-resizer__handle-dots {
+      transition: opacity 0.2s ease-in;
+      opacity: 0;
+    }
+  }
+}
+
+.panel-editor-resizer__handle-dots {
+  border-top: 2px dashed $text-color-faint;
+  position: relative;
+  top: 4px;
+}
+
+.viz-picker {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 13px;
+}
+
+.viz-picker__item {
+  background: $card-background;
+  box-shadow: $card-shadow;
+
+  border-radius: 3px;
+  height: 90px;
+  width: 150px;
+  flex-shrink: 0;
+  flex-direction: column;
+  text-align: center;
+  cursor: pointer;
+  display: flex;
+  margin-right: 10px;
+  margin-bottom: 10px;
+  border: 1px solid transparent;
+  align-items: center;
+
+  &:hover {
+    background: $card-background-hover;
+  }
+
+  &--selected {
+    box-shadow: 0 0 12px #ff4d00;
+  }
+}
+
+.viz-picker__item-name {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: $font-size-sm;
+  display: flex;
+  flex-direction: column;
+  align-self: center;
+  height: 23px;
+}
+
+.viz-picker__item-img {
+  height: 55px;
+}
+
+.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;
+
+  @include clearfix();
+
+  .active.gf-tabs-link {
+    background: $toolbar-tab-bg;
+  }
+}
+
+.panel-editor-tabs__close {
+  padding: 5px 9px;
+  border-radius: $border-radius;
+  float: right;
+  @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
+}
+
+.ds-picker-list {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 13px;
+  flex-direction: column;
+}
+
+.ds-picker-list__item {
+  background: $card-background;
+  box-shadow: $card-shadow;
+
+  border-radius: 3px;
+  display: flex;
+  cursor: pointer;
+  margin-bottom: 3px;
+  padding: 5px 15px;
+  align-items: center;
+
+  &:hover {
+    background: $card-background-hover;
+  }
+
+  &--selected {
+    .ds-picker-list__name {
+      color: $text-color;
+    }
+  }
+}
+
+.ds-picker-list__name {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: $font-size-md;
+  padding-left: 15px;
+}
+
+.ds-picker-list__img {
+  width: 30px;
+}
+
+.form-option-box {
+  margin-bottom: 20px;
+}
+
+.form-option-box__header {
+  border-bottom: 2px solid $dark-4;
+  padding: 5px 0px;
+  font-size: $font-size-md;
+  margin-bottom: 20px;
+}

+ 71 - 71
public/sass/components/_scrollbar.scss

@@ -106,78 +106,78 @@
   opacity: 0.9;
   opacity: 0.9;
 }
 }
 
 
-// Scrollbars
+// // Scrollbars
+// //
+//
+// ::-webkit-scrollbar {
+//   width: 8px;
+//   height: 8px;
+// }
+//
+// ::-webkit-scrollbar:hover {
+//   height: 8px;
+// }
+//
+// ::-webkit-scrollbar-button:start:decrement,
+// ::-webkit-scrollbar-button:end:increment {
+//   display: none;
+// }
+// ::-webkit-scrollbar-button:horizontal:decrement {
+//   display: none;
+// }
+// ::-webkit-scrollbar-button:horizontal:increment {
+//   display: none;
+// }
+// ::-webkit-scrollbar-button:vertical:decrement {
+//   display: none;
+// }
+// ::-webkit-scrollbar-button:vertical:increment {
+//   display: none;
+// }
+// ::-webkit-scrollbar-button:horizontal:decrement:active {
+//   background-image: none;
+// }
+// ::-webkit-scrollbar-button:horizontal:increment:active {
+//   background-image: none;
+// }
+// ::-webkit-scrollbar-button:vertical:decrement:active {
+//   background-image: none;
+// }
+// ::-webkit-scrollbar-button:vertical:increment:active {
+//   background-image: none;
+// }
+// ::-webkit-scrollbar-track-piece {
+//   background-color: transparent;
+// }
+//
+// ::-webkit-scrollbar-thumb:vertical {
+//   height: 50px;
+//   background: -webkit-gradient(
+//     linear,
+//     left top,
+//     right top,
+//     color-stop(0%, $scrollbarBackground),
+//     color-stop(100%, $scrollbarBackground2)
+//   );
+//   border: 1px solid $scrollbarBorder;
+//   border-top: 1px solid $scrollbarBorder;
+//   border-left: 1px solid $scrollbarBorder;
+// }
+//
+// ::-webkit-scrollbar-thumb:horizontal {
+//   width: 50px;
+//   background: -webkit-gradient(
+//     linear,
+//     left top,
+//     left bottom,
+//     color-stop(0%, $scrollbarBackground),
+//     color-stop(100%, $scrollbarBackground2)
+//   );
+//   border: 1px solid $scrollbarBorder;
+//   border-top: 1px solid $scrollbarBorder;
+//   border-left: 1px solid $scrollbarBorder;
+// }
 //
 //
-
-::-webkit-scrollbar {
-  width: 8px;
-  height: 8px;
-}
-
-::-webkit-scrollbar:hover {
-  height: 8px;
-}
-
-::-webkit-scrollbar-button:start:decrement,
-::-webkit-scrollbar-button:end:increment {
-  display: none;
-}
-::-webkit-scrollbar-button:horizontal:decrement {
-  display: none;
-}
-::-webkit-scrollbar-button:horizontal:increment {
-  display: none;
-}
-::-webkit-scrollbar-button:vertical:decrement {
-  display: none;
-}
-::-webkit-scrollbar-button:vertical:increment {
-  display: none;
-}
-::-webkit-scrollbar-button:horizontal:decrement:active {
-  background-image: none;
-}
-::-webkit-scrollbar-button:horizontal:increment:active {
-  background-image: none;
-}
-::-webkit-scrollbar-button:vertical:decrement:active {
-  background-image: none;
-}
-::-webkit-scrollbar-button:vertical:increment:active {
-  background-image: none;
-}
-::-webkit-scrollbar-track-piece {
-  background-color: transparent;
-}
-
-::-webkit-scrollbar-thumb:vertical {
-  height: 50px;
-  background: -webkit-gradient(
-    linear,
-    left top,
-    right top,
-    color-stop(0%, $scrollbarBackground),
-    color-stop(100%, $scrollbarBackground2)
-  );
-  border: 1px solid $scrollbarBorder;
-  border-top: 1px solid $scrollbarBorder;
-  border-left: 1px solid $scrollbarBorder;
-}
-
-::-webkit-scrollbar-thumb:horizontal {
-  width: 50px;
-  background: -webkit-gradient(
-    linear,
-    left top,
-    left bottom,
-    color-stop(0%, $scrollbarBackground),
-    color-stop(100%, $scrollbarBackground2)
-  );
-  border: 1px solid $scrollbarBorder;
-  border-top: 1px solid $scrollbarBorder;
-  border-left: 1px solid $scrollbarBorder;
-}
-
 // Baron styles
 // Baron styles
 
 
 .baron {
 .baron {

+ 1 - 2
public/sass/components/_submenu.scss

@@ -4,8 +4,7 @@
   flex-wrap: wrap;
   flex-wrap: wrap;
   align-content: flex-start;
   align-content: flex-start;
   align-items: flex-start;
   align-items: flex-start;
-
-  margin: 0 0 $panel-margin 0;
+  padding: 0 0 $panel-margin 0;
 }
 }
 
 
 .annotation-disabled,
 .annotation-disabled,

+ 4 - 2
public/sass/components/_tabbed_view.scss

@@ -2,9 +2,10 @@
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   height: 100%;
   height: 100%;
+  flex-grow: 1;
 
 
   &.tabbed-view--new {
   &.tabbed-view--new {
-    padding: 25px 0 0 0;
+    padding: 0 0 0 0;
     height: 100%;
     height: 100%;
   }
   }
 }
 }
@@ -12,13 +13,14 @@
 .tabbed-view-header {
 .tabbed-view-header {
   box-shadow: $page-header-shadow;
   box-shadow: $page-header-shadow;
   border-bottom: 1px solid $page-header-border-color;
   border-bottom: 1px solid $page-header-border-color;
+  padding: 0 $dashboard-padding;
   @include clearfix();
   @include clearfix();
 }
 }
 
 
 .tabbed-view-title {
 .tabbed-view-title {
   float: left;
   float: left;
   padding-top: 0.5rem;
   padding-top: 0.5rem;
-  margin: 0 $spacer*3 0 $spacer*1;
+  margin: 0 $spacer*3 0 0;
 }
 }
 
 
 .tabbed-view-panel-title {
 .tabbed-view-panel-title {

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

@@ -53,6 +53,7 @@
       background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
       background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
     }
     }
   }
   }
+
   &.active--panel {
   &.active--panel {
     background: $panel-bg !important;
     background: $panel-bg !important;
   }
   }

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

@@ -20,6 +20,7 @@
   background-color: $page-bg;
   background-color: $page-bg;
   border-radius: 0 0 0 4px;
   border-radius: 0 0 0 4px;
   box-shadow: $search-shadow;
   box-shadow: $search-shadow;
+  z-index: $zindex-dropdown;
 }
 }
 
 
 .gf-timepicker-absolute-section {
 .gf-timepicker-absolute-section {

+ 59 - 0
public/sass/components/_toolbar.scss

@@ -0,0 +1,59 @@
+.toolbar {
+  display: flex;
+  align-content: center;
+  align-items: center;
+  background: $toolbar-bg;
+  box-shadow: $toolbar-shadow;
+  padding: 7px 20px 7px 20px;
+  position: relative;
+  z-index: 1;
+  flex: 0 0 auto;
+}
+
+.toolbar__main {
+  padding: $input-padding-y $input-padding-x;
+  font-size: $font-size-md;
+  line-height: $input-line-height;
+  color: $input-color;
+  background-color: $input-bg;
+  border: $input-border;
+  border-radius: $input-border-radius;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+
+  .fa {
+    margin-left: 20px;
+    display: inline-block;
+    position: relative;
+  }
+}
+
+.toolbar__main-image {
+  margin-right: 10px;
+  display: inline-block;
+  width: 20px;
+  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;
+  top: 20px;
+  font-size: $font-size-md;
+
+  &:hover {
+    color: $text-color-strong;
+  }
+}

+ 0 - 81
public/sass/components/_viz_editor.scss

@@ -1,81 +0,0 @@
-.viz-editor {
-  display: flex;
-  height: 100%;
-}
-
-.viz-editor-col1 {
-  width: 210px;
-  height: 100%;
-  margin-right: 40px;
-}
-
-.viz-editor-col2 {
-  flex-grow: 1;
-}
-
-.viz-picker {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-
-.viz-picker__search {
-  flex-grow: 0;
-}
-
-.viz-picker__items {
-  flex-grow: 1;
-  height: calc(100% - 50px);
-}
-
-.viz-picker__item {
-  background: $card-background;
-  box-shadow: $card-shadow;
-
-  border-radius: 3px;
-  padding: $spacer;
-  width: 100%;
-  height: 60px;
-  text-align: center;
-  margin-bottom: 6px;
-  cursor: pointer;
-  display: flex;
-  flex-shrink: 0;
-  border: 1px solid transparent;
-  @include left-brand-border;
-
-  &:hover {
-    background: $card-background-hover;
-  }
-
-  &--selected {
-    // border: 1px solid $orange;
-    @include left-brand-border-gradient();
-
-    .viz-picker__item-name {
-      color: $text-color;
-    }
-
-    .viz-picker__item-img {
-      filter: saturate(100%);
-    }
-  }
-}
-
-.viz-picker__item-name {
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-  font-size: $font-size-h5;
-  display: flex;
-  flex-direction: column;
-  align-self: center;
-  padding-left: $spacer;
-  font-size: $font-size-md;
-  color: $text-muted;
-}
-
-.viz-picker__item-img {
-  height: 100%;
-  filter: saturate(30%);
-}

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

@@ -37,20 +37,6 @@ div.flot-text {
   height: 100%;
   height: 100%;
 }
 }
 
 
-.panel-editor-container {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-
-.panel-editor-container__panel {
-  height: 35%;
-}
-
-.panel-editor-container__editor {
-  height: 65%;
-}
-
 .panel-container {
 .panel-container {
   background-color: $panel-bg;
   background-color: $panel-bg;
   border: $panel-border;
   border: $panel-border;