mirror of
https://github.com/grafana/grafana.git
synced 2026-08-10 05:08:16 -05:00
Merge branch 'pane-edit-ux-tabs' into develop
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -23,7 +23,7 @@ export default ({ children, in: inProp, maxHeight = defaultMaxHeight, style = de
|
||||
const transitionStyles = {
|
||||
exited: { maxHeight: 0 },
|
||||
entering: { maxHeight: maxHeight },
|
||||
entered: { maxHeight: maxHeight, overflow: 'visible' },
|
||||
entered: { maxHeight: 'unset', overflow: 'visible' },
|
||||
exiting: { maxHeight: 0 },
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Props {
|
||||
class CustomScrollbar extends PureComponent<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
customClassName: 'custom-scrollbars',
|
||||
autoHide: true,
|
||||
autoHide: false,
|
||||
autoHideTimeout: 200,
|
||||
autoHideDuration: 200,
|
||||
hideTracksWhenNotNeeded: false,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import coreModule from '../core_module';
|
||||
|
||||
@@ -5,18 +6,20 @@ import coreModule from '../core_module';
|
||||
function dashClass($timeout) {
|
||||
return {
|
||||
link: ($scope, elem) => {
|
||||
const body = $('body');
|
||||
|
||||
$scope.ctrl.dashboard.events.on('view-mode-changed', panel => {
|
||||
console.log('view-mode-changed', panel.fullscreen);
|
||||
if (panel.fullscreen) {
|
||||
elem.addClass('panel-in-fullscreen');
|
||||
body.addClass('panel-in-fullscreen');
|
||||
} else {
|
||||
$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 => {
|
||||
if (newValue) {
|
||||
|
||||
@@ -18,6 +18,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS
|
||||
|
||||
if (action.payload.partial) {
|
||||
query = _.defaults(query, state.query);
|
||||
query = _.omitBy(query, _.isNull);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@ import classNames from 'classnames';
|
||||
import sizeMe from 'react-sizeme';
|
||||
|
||||
let lastGridWidth = 1200;
|
||||
let ignoreNextWidthChange = false;
|
||||
|
||||
function GridWrapper({
|
||||
size,
|
||||
@@ -24,8 +25,12 @@ function GridWrapper({
|
||||
isFullscreen,
|
||||
}) {
|
||||
const width = size.width > 0 ? size.width : lastGridWidth;
|
||||
|
||||
// logic to ignore width changes (optimization)
|
||||
if (width !== lastGridWidth) {
|
||||
if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
|
||||
if (ignoreNextWidthChange) {
|
||||
ignoreNextWidthChange = false;
|
||||
} else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
|
||||
onWidthChange();
|
||||
lastGridWidth = width;
|
||||
}
|
||||
@@ -138,6 +143,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
}
|
||||
|
||||
onViewModeChanged(payload) {
|
||||
ignoreNextWidthChange = true;
|
||||
this.setState({ animated: !payload.fullscreen });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
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 { DashboardRow } from './DashboardRow';
|
||||
import { AddPanelPanel } from './AddPanelPanel';
|
||||
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 { PanelEditor } from './PanelEditor';
|
||||
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
@@ -18,20 +21,19 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface State {
|
||||
pluginExports: PluginExports;
|
||||
plugin: PanelPlugin;
|
||||
}
|
||||
|
||||
export class DashboardPanel extends PureComponent<Props, State> {
|
||||
element: any;
|
||||
angularPanel: AngularComponent;
|
||||
pluginInfo: any;
|
||||
specialPanels = {};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pluginExports: null,
|
||||
plugin: null,
|
||||
};
|
||||
|
||||
this.specialPanels['row'] = this.renderRow.bind(this);
|
||||
@@ -64,20 +66,22 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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];
|
||||
const { panel } = this.props;
|
||||
|
||||
if (this.pluginInfo.exports) {
|
||||
// handle plugin loading & changing of plugin type
|
||||
if (!this.state.plugin || this.state.plugin.id !== panel.type) {
|
||||
const plugin = config.panels[panel.type];
|
||||
|
||||
if (plugin.exports) {
|
||||
this.cleanUpAngularPanel();
|
||||
this.setState({ pluginExports: this.pluginInfo.exports });
|
||||
this.setState({ plugin: plugin });
|
||||
} else {
|
||||
importPluginModule(this.pluginInfo.module).then(pluginExports => {
|
||||
importPluginModule(plugin.module).then(pluginExports => {
|
||||
this.cleanUpAngularPanel();
|
||||
// cache plugin exports (saves a promise async cycle next time)
|
||||
this.pluginInfo.exports = pluginExports;
|
||||
plugin.exports = pluginExports;
|
||||
// update panel state
|
||||
this.setState({ pluginExports: pluginExports });
|
||||
this.setState({ plugin: plugin });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -113,7 +117,9 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
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 panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
|
||||
// this might look strange with these classes that change when edit, but
|
||||
@@ -121,37 +127,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<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>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panel } = this.props;
|
||||
const { plugin } = this.state;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (this.state.pluginExports.PanelComponent) {
|
||||
// if exporting PanelComponent it must be a react panel
|
||||
if (plugin.exports.PanelComponent) {
|
||||
return this.renderReactPanel();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,19 @@ import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { QueriesTab } from './QueriesTab';
|
||||
import { VizTypePicker } from './VizTypePicker';
|
||||
import { VisualizationTab } from './VisualizationTab';
|
||||
|
||||
import { store } from 'app/store/store';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { PanelPlugin, PluginExports } from 'app/types/plugins';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
|
||||
interface PanelEditorProps {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
panelType: string;
|
||||
pluginExports: PluginExports;
|
||||
plugin: PanelPlugin;
|
||||
onTypeChanged: (newType: PanelPlugin) => void;
|
||||
}
|
||||
|
||||
@@ -34,43 +33,10 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
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' },
|
||||
];
|
||||
}
|
||||
|
||||
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) => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
@@ -81,28 +47,44 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: { tab: null, fullscreen: null, edit: null },
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { panel, dashboard, onTypeChanged, plugin } = this.props;
|
||||
const { location } = store.getState();
|
||||
const activeTab = location.query.tab || 'queries';
|
||||
|
||||
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">
|
||||
{this.tabs.map(tab => {
|
||||
return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
|
||||
})}
|
||||
</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>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -121,8 +103,8 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) {
|
||||
});
|
||||
|
||||
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}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Services & utils
|
||||
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
|
||||
import { EditorTabBody } from './EditorTabBody';
|
||||
import { DataSourcePicker } from './DataSourcePicker';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
|
||||
@@ -48,6 +47,27 @@ export class QueriesTab extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import config from 'app/core/config';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
|
||||
import _ from 'lodash';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
|
||||
interface Props {
|
||||
currentType: string;
|
||||
current: PanelPlugin;
|
||||
onTypeChanged: (newType: PanelPlugin) => void;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ interface State {
|
||||
}
|
||||
|
||||
export class VizTypePicker extends PureComponent<Props, State> {
|
||||
searchInput: HTMLElement;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -36,34 +38,55 @@ export class VizTypePicker extends PureComponent<Props, State> {
|
||||
renderVizPlugin = (plugin, index) => {
|
||||
const cssClass = classNames({
|
||||
'viz-picker__item': true,
|
||||
'viz-picker__item--selected': plugin.id === this.props.currentType,
|
||||
'viz-picker__item--selected': plugin.id === this.props.current.id,
|
||||
});
|
||||
|
||||
return (
|
||||
<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>
|
||||
<img className="viz-picker__item-img" src={plugin.info.logos.small} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.searchInput.focus();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
renderFilters() {
|
||||
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 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 className="viz-picker">{pluginList.map(this.renderVizPlugin)}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ const panelTemplate = `
|
||||
</li>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
// Utils
|
||||
import config from 'app/core/config';
|
||||
import { importPluginModule } from './plugin_loader';
|
||||
|
||||
// Types
|
||||
import { DataSourceApi } from 'app/types/series';
|
||||
import { DataSource } from 'app/types';
|
||||
import { DataSource, DataSourceSelectItem } from 'app/types';
|
||||
|
||||
export class DatasourceSrv {
|
||||
datasources: { [name: string]: DataSource };
|
||||
@@ -102,8 +99,8 @@ export class DatasourceSrv {
|
||||
return _.sortBy(es, ['name']);
|
||||
}
|
||||
|
||||
getMetricSources(options) {
|
||||
const metricSources = [];
|
||||
getMetricSources(options?) {
|
||||
const metricSources: DataSourceSelectItem[] = [];
|
||||
|
||||
_.each(config.datasources, (value, key) => {
|
||||
if (value.meta && value.meta.metrics) {
|
||||
|
||||
@@ -61,11 +61,26 @@ export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> {
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,13 @@ export interface DataSource {
|
||||
testDatasource?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export interface DataSourceSelectItem {
|
||||
name: string;
|
||||
value: string | null;
|
||||
meta: PluginMeta;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
export interface DataSourcesState {
|
||||
dataSources: DataSource[];
|
||||
searchQuery: string;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DashboardState } from './dashboard';
|
||||
import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
|
||||
import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
|
||||
import { Invitee, OrgUser, User, UsersState, UserState } from './user';
|
||||
import { DataSource, DataSourcesState } from './datasources';
|
||||
import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
|
||||
import {
|
||||
TimeRange,
|
||||
LoadingState,
|
||||
@@ -55,6 +55,7 @@ export {
|
||||
OrgRole,
|
||||
PermissionLevel,
|
||||
DataSource,
|
||||
DataSourceSelectItem,
|
||||
PluginMeta,
|
||||
ApiKey,
|
||||
ApiKeysState,
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
@import 'components/form_select_box';
|
||||
@import 'components/user-picker';
|
||||
@import 'components/description-picker';
|
||||
@import 'components/viz_editor';
|
||||
@import 'components/panel_editor';
|
||||
@import 'components/toolbar';
|
||||
@import 'components/delete_button';
|
||||
@import 'components/add_data_source.scss';
|
||||
@import 'components/page_loader';
|
||||
|
||||
@@ -77,6 +77,7 @@ $brand-gradient: linear-gradient(
|
||||
rgba(255, 68, 0, 0.7) 99%,
|
||||
rgba(255, 68, 0, 0.7) 100%
|
||||
);
|
||||
|
||||
$page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
|
||||
|
||||
// Links
|
||||
@@ -110,7 +111,6 @@ $divider-border-color: #555;
|
||||
|
||||
// Graphite Target Editor
|
||||
$tight-form-bg: $dark-3;
|
||||
|
||||
$tight-form-func-bg: #333334;
|
||||
$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-link-color: $text-color;
|
||||
$list-item-shadow: $card-shadow;
|
||||
|
||||
$empty-list-cta-bg: $gray-blue;
|
||||
|
||||
// Scrollbars
|
||||
@@ -152,8 +153,8 @@ $table-bg-hover: $dark-3;
|
||||
$btn-primary-bg: #ff6600;
|
||||
$btn-primary-bg-hl: #bc3e06;
|
||||
|
||||
$btn-secondary-bg: $blue-dark;
|
||||
$btn-secondary-bg-hl: lighten($blue-dark, 5%);
|
||||
$btn-secondary-bg: $blue-dark;
|
||||
|
||||
$btn-success-bg: $green;
|
||||
$btn-success-bg-hl: darken($green, 6%);
|
||||
@@ -266,6 +267,11 @@ $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;
|
||||
|
||||
// Pagination
|
||||
// -------------------------
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ $white: #fff;
|
||||
// Accent colors
|
||||
// -------------------------
|
||||
$blue: #0083b3;
|
||||
$blue-dark: #005f81;
|
||||
$blue-light: #00a8e6;
|
||||
$green: #3aa655;
|
||||
$red: #d44939;
|
||||
@@ -213,6 +214,11 @@ $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;
|
||||
|
||||
// search
|
||||
$search-shadow: 0 5px 30px 0 $gray-4;
|
||||
$search-filter-box-bg: $gray-7;
|
||||
|
||||
@@ -120,8 +120,8 @@
|
||||
// Info appears as a neutral blue
|
||||
.btn-secondary {
|
||||
@include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl);
|
||||
// Inverse appears as dark gray
|
||||
}
|
||||
// Inverse appears as dark gray
|
||||
.btn-inverse {
|
||||
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow);
|
||||
//background: $card-background;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.panel-in-fullscreen {
|
||||
.react-grid-layout {
|
||||
height: 100% !important;
|
||||
height: calc(100% - 20px) !important;
|
||||
}
|
||||
|
||||
.react-grid-item {
|
||||
@@ -19,6 +19,10 @@
|
||||
transform: translate(0px, 0px) !important;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
// Disable grid interaction indicators in fullscreen panels
|
||||
.panel-header:hover {
|
||||
background-color: inherit;
|
||||
|
||||
@@ -415,7 +415,27 @@ select.gf-form-input ~ .gf-form-help-icon {
|
||||
}
|
||||
|
||||
.cta-form__close {
|
||||
background: transparent;
|
||||
padding: 4px 8px 4px 9px;
|
||||
border: none;
|
||||
position: absolute;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
.panel-in-fullscreen {
|
||||
.navbar {
|
||||
@include navbar-alt-look();
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.navbar-button--add-panel,
|
||||
@@ -50,10 +50,6 @@
|
||||
.navbar-page-btn .fa-caret-down {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-buttons--close {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-page-btn {
|
||||
@@ -98,7 +94,7 @@
|
||||
}
|
||||
|
||||
.navbar-buttons {
|
||||
height: $navbarHeight;
|
||||
// height: $navbarHeight;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -106,78 +106,78 @@
|
||||
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 {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
margin: 0 0 $panel-margin 0;
|
||||
padding: 0 0 $panel-margin 0;
|
||||
}
|
||||
|
||||
.annotation-disabled,
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
&.tabbed-view--new {
|
||||
padding: 25px 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -12,13 +13,14 @@
|
||||
.tabbed-view-header {
|
||||
box-shadow: $page-header-shadow;
|
||||
border-bottom: 1px solid $page-header-border-color;
|
||||
padding: 0 $dashboard-padding;
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
.tabbed-view-title {
|
||||
float: left;
|
||||
padding-top: 0.5rem;
|
||||
margin: 0 $spacer*3 0 $spacer*1;
|
||||
margin: 0 $spacer*3 0 0;
|
||||
}
|
||||
|
||||
.tabbed-view-panel-title {
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&.active--panel {
|
||||
background: $panel-bg !important;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
background-color: $page-bg;
|
||||
border-radius: 0 0 0 4px;
|
||||
box-shadow: $search-shadow;
|
||||
z-index: $zindex-dropdown;
|
||||
}
|
||||
|
||||
.gf-timepicker-absolute-section {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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%);
|
||||
}
|
||||
@@ -37,20 +37,6 @@ div.flot-text {
|
||||
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 {
|
||||
background-color: $panel-bg;
|
||||
border: $panel-border;
|
||||
|
||||
Reference in New Issue
Block a user