Commit e052e165 by Torkel Ödegaard

Merge branch 'react-panels' of github.com:grafana/grafana into react-panels

parents 4fd21070 51f8d3ca
...@@ -7,9 +7,20 @@ export interface BuildInfo { ...@@ -7,9 +7,20 @@ export interface BuildInfo {
env: string; env: string;
} }
export interface PanelPlugin {
id: string;
name: string;
meta: any;
hideFromList: boolean;
module: string;
baseUrl: string;
info: any;
sort: number;
}
export class Settings { export class Settings {
datasources: any; datasources: any;
panels: any; panels: PanelPlugin[];
appSubUrl: string; appSubUrl: string;
window_title_prefix: string; window_title_prefix: string;
buildInfo: BuildInfo; buildInfo: BuildInfo;
......
...@@ -139,7 +139,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> { ...@@ -139,7 +139,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
} }
onViewModeChanged(payload) { onViewModeChanged(payload) {
this.setState({ animated: payload.fullscreen }); this.setState({ animated: !payload.fullscreen });
} }
updateGridPos(item, layout) { updateGridPos(item, layout) {
......
...@@ -5,24 +5,27 @@ import { DashboardModel } from '../dashboard_model'; ...@@ -5,24 +5,27 @@ import { DashboardModel } from '../dashboard_model';
import { getAngularLoader, AngularComponent } from 'app/core/services/angular_loader'; import { getAngularLoader, AngularComponent } from 'app/core/services/angular_loader';
import { DashboardRow } from './DashboardRow'; import { DashboardRow } from './DashboardRow';
import { AddPanelPanel } from './AddPanelPanel'; import { AddPanelPanel } from './AddPanelPanel';
import { importPluginModule } from 'app/features/plugins/plugin_loader'; import { importPluginModule, PluginExports } from 'app/features/plugins/plugin_loader';
import { PanelChrome } from './PanelChrome'; import { PanelChrome } from './PanelChrome';
export interface DashboardPanelProps { export interface Props {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
} }
export class DashboardPanel extends React.Component<DashboardPanelProps, any> { export interface State {
pluginExports: PluginExports;
}
export class DashboardPanel extends React.Component<Props, State> {
element: any; element: any;
angularPanel: AngularComponent; angularPanel: AngularComponent;
pluginInfo: any; pluginInfo: any;
pluginExports: any;
specialPanels = {}; specialPanels = {};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {}; this.state = { pluginExports: null };
this.specialPanels['row'] = this.renderRow.bind(this); this.specialPanels['row'] = this.renderRow.bind(this);
this.specialPanels['add-panel'] = this.renderAddPanel.bind(this); this.specialPanels['add-panel'] = this.renderAddPanel.bind(this);
...@@ -32,8 +35,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> { ...@@ -32,8 +35,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
// load panel plugin // load panel plugin
importPluginModule(this.pluginInfo.module).then(pluginExports => { importPluginModule(this.pluginInfo.module).then(pluginExports => {
this.pluginExports = pluginExports; this.setState({ pluginExports: pluginExports });
this.forceUpdate();
}); });
} }
} }
...@@ -51,8 +53,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> { ...@@ -51,8 +53,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
} }
componentDidUpdate() { componentDidUpdate() {
// skip loading angular component if we have no element // skip loading angular component if we have no element or we have already loaded it
// or we have already loaded it
if (!this.element || this.angularPanel) { if (!this.element || this.angularPanel) {
return; return;
} }
...@@ -70,18 +71,20 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> { ...@@ -70,18 +71,20 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
} }
render() { render() {
const { pluginExports } = this.state;
if (this.isSpecial()) { if (this.isSpecial()) {
return this.specialPanels[this.props.panel.type](); return this.specialPanels[this.props.panel.type]();
} }
if (!this.pluginExports) { if (!pluginExports) {
return null; return null;
} }
if (this.pluginExports.PanelComponent) { if (pluginExports.PanelComponent) {
return ( return (
<PanelChrome <PanelChrome
component={this.pluginExports.PanelComponent} component={pluginExports.PanelComponent}
panel={this.props.panel} panel={this.props.panel}
dashboard={this.props.dashboard} dashboard={this.props.dashboard}
/> />
......
import React, { Component, ComponentClass } from 'react';
export interface OuterProps {
type: string;
queries: any[];
isVisible: boolean;
}
export interface PanelProps extends OuterProps {
data: any[];
}
export interface DataPanel extends ComponentClass<OuterProps> {
}
interface State {
isLoading: boolean;
data: any[];
}
export const DataPanelWrapper = (ComposedComponent: ComponentClass<PanelProps>) => {
class Wrapper extends Component<OuterProps, State> {
public static defaultProps = {
isVisible: true,
};
constructor(props: OuterProps) {
super(props);
this.state = {
isLoading: false,
data: [],
};
}
public componentDidMount() {
console.log('data panel mount');
this.issueQueries();
}
public issueQueries = async () => {
const { isVisible } = this.props;
if (!isVisible) {
return;
}
this.setState({ isLoading: true });
await new Promise(resolve => {
setTimeout(() => {
this.setState({ isLoading: false, data: [{value: 10}] });
}, 500);
});
};
public render() {
const { data, isLoading } = this.state;
console.log('data panel render');
if (!data.length) {
return (
<div className="no-data">
<p>No Data</p>
</div>
);
}
if (isLoading) {
return (
<div className="loading">
<p>Loading</p>
</div>
);
}
return <ComposedComponent {...this.props} data={data} />;
}
}
return Wrapper;
};
import React from 'react'; import React, { ComponentClass } from 'react';
import $ from 'jquery'; import $ from 'jquery';
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model'; import { DashboardModel } from '../dashboard_model';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
import { PanelHeader } from './PanelHeader'; import { PanelHeader } from './PanelHeader';
import { PanelEditor } from './PanelEditor'; import { PanelEditor } from './PanelEditor';
import { DataPanel, PanelProps, DataPanelWrapper } from './DataPanel';
const TITLE_HEIGHT = 27; const TITLE_HEIGHT = 27;
const PANEL_BORDER = 2; const PANEL_BORDER = 2;
export interface PanelChromeProps { export interface Props {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
component: any; component: ComponentClass<PanelProps>;
} }
export class PanelChrome extends React.Component<PanelChromeProps, any> { interface State {
height: number;
}
export class PanelChrome extends React.Component<Props, State> {
panelComponent: DataPanel;
constructor(props) { constructor(props) {
super(props); super(props);
this.props.panel.events.on('panel-size-changed', this.triggerForceUpdate.bind(this)); this.state = {
} height: this.getPanelHeight(),
};
triggerForceUpdate() { this.panelComponent = DataPanelWrapper(this.props.component);
this.forceUpdate(); this.props.panel.events.on('panel-size-changed', this.onPanelSizeChanged);
} }
render() { onPanelSizeChanged = () => {
let panelContentStyle = { this.setState({
height: this.getPanelHeight(), height: this.getPanelHeight(),
});
}; };
let PanelComponent = this.props.component; componentDidMount() {
console.log('panel chrome mounted');
}
render() {
let PanelComponent = this.panelComponent;
return ( return (
<div className="panel-height-helper"> <div className="panel-editor-container">
<div className="panel-container"> <div className="panel-container">
<PanelHeader panel={this.props.panel} dashboard={this.props.dashboard} /> <PanelHeader panel={this.props.panel} dashboard={this.props.dashboard} />
<div className="panel-content" style={panelContentStyle}> <div className="panel-content" style={{ height: this.state.height }}>
{<PanelComponent />} {<PanelComponent type={'test'} queries={[]} isVisible={true} />}
</div> </div>
</div> </div>
{this.props.panel.isEditing && <PanelEditor panel={this.props.panel} dashboard={this.props.dashboard} />} {this.props.panel.isEditing && <PanelEditor panel={this.props.panel} dashboard={this.props.dashboard} />}
...@@ -59,6 +73,6 @@ export class PanelChrome extends React.Component<PanelChromeProps, any> { ...@@ -59,6 +73,6 @@ export class PanelChrome extends React.Component<PanelChromeProps, any> {
height = panel.gridPos.h * GRID_CELL_HEIGHT + (panel.gridPos.h - 1) * GRID_CELL_VMARGIN; height = panel.gridPos.h * GRID_CELL_HEIGHT + (panel.gridPos.h - 1) * GRID_CELL_VMARGIN;
} }
return height - PANEL_BORDER + TITLE_HEIGHT; return height - (PANEL_BORDER + TITLE_HEIGHT);
} }
} }
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model'; import { DashboardModel } from '../dashboard_model';
import { getAngularLoader, AngularComponent } from 'app/core/services/angular_loader'; import { store } from 'app/stores/store';
import { observer } from 'mobx-react';
import { QueriesTab } from './QueriesTab';
import { PanelPlugin } from 'app/core/config';
import { VizTypePicker } from './VizTypePicker';
interface PanelEditorProps { interface PanelEditorProps {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
} }
interface PanelEditorTab {
id: string;
text: string;
icon: string;
}
@observer
export class PanelEditor extends React.Component<PanelEditorProps, any> { export class PanelEditor extends React.Component<PanelEditorProps, any> {
queryElement: any; tabs: PanelEditorTab[];
queryComp: AngularComponent;
tabs: any[];
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -22,40 +32,42 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> { ...@@ -22,40 +32,42 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
]; ];
} }
componentDidMount() { renderQueriesTab() {
if (!this.queryElement) { return <QueriesTab panel={this.props.panel} dashboard={this.props.dashboard} />;
return;
} }
let loader = getAngularLoader(); renderVizTab() {
var template = '<metrics-tab />'; return (
let scopeProps = { <div className="viz-editor">
ctrl: { <div className="viz-editor-col1">
panel: this.props.panel, <VizTypePicker currentType={this.props.panel.type} onTypeChanged={this.onVizTypeChanged} />
dashboard: this.props.dashboard, </div>
panelCtrl: { <div className="viz-editor-col2">
panel: this.props.panel, <h5 className="page-heading">Options</h5>
dashboard: this.props.dashboard, </div>
}, </div>
}, );
};
this.queryComp = loader.load(this.queryElement, scopeProps, template);
} }
onChangeTab = tabName => {}; onVizTypeChanged = (plugin: PanelPlugin) => {
this.props.panel.type = plugin.id;
this.forceUpdate();
};
onChangeTab = (tab: PanelEditorTab) => {
store.view.updateQuery({ tab: tab.id }, false);
};
render() { render() {
const activeTab: string = store.view.query.get('tab') || 'queries';
return ( return (
<div className="tabbed-view tabbed-view--panel-edit-new"> <div className="tabbed-view tabbed-view--new">
<div className="tabbed-view-header"> <div className="tabbed-view-header">
<ul className="gf-tabs"> <ul className="gf-tabs">
<li className="gf-tabs-item"> {this.tabs.map(tab => {
<a className="gf-tabs-link active">Queries</a> return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
</li> })}
<li className="gf-tabs-item">
<a className="gf-tabs-link">Visualization</a>
</li>
</ul> </ul>
<button className="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();"> <button className="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
...@@ -64,9 +76,32 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> { ...@@ -64,9 +76,32 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
</div> </div>
<div className="tabbed-view-body"> <div className="tabbed-view-body">
<div ref={element => (this.queryElement = element)} className="panel-height-helper" /> {activeTab === 'queries' && this.renderQueriesTab()}
{activeTab === 'viz' && this.renderVizTab()}
</div> </div>
</div> </div>
); );
} }
} }
interface TabItemParams {
tab: PanelEditorTab;
activeTab: string;
onClick: (tab: PanelEditorTab) => void;
}
function TabItem({ tab, activeTab, onClick }: TabItemParams) {
const tabClasses = classNames({
'gf-tabs-link': true,
active: activeTab === tab.id,
});
return (
<li className="gf-tabs-item" key={tab.id}>
<a className={tabClasses} onClick={() => onClick(tab)}>
<i className={tab.icon} />
{tab.text}
</a>
</li>
);
}
...@@ -11,11 +11,14 @@ interface PanelHeaderProps { ...@@ -11,11 +11,14 @@ interface PanelHeaderProps {
export class PanelHeader extends React.Component<PanelHeaderProps, any> { export class PanelHeader extends React.Component<PanelHeaderProps, any> {
onEditPanel = () => { onEditPanel = () => {
store.view.updateQuery({ store.view.updateQuery(
{
panelId: this.props.panel.id, panelId: this.props.panel.id,
edit: true, edit: true,
fullscreen: true, fullscreen: true,
}); },
false
);
}; };
render() { render() {
......
import React from 'react';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { getAngularLoader, AngularComponent } from 'app/core/services/angular_loader';
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
}
export class QueriesTab extends React.Component<Props, any> {
element: any;
component: AngularComponent;
constructor(props) {
super(props);
}
componentDidMount() {
if (!this.element) {
return;
}
let loader = getAngularLoader();
var template = '<metrics-tab />';
let scopeProps = {
ctrl: {
panel: this.props.panel,
dashboard: this.props.dashboard,
panelCtrl: {
panel: this.props.panel,
dashboard: this.props.dashboard,
},
},
};
this.component = loader.load(this.element, scopeProps, template);
}
componentWillUnmount() {
if (this.component) {
this.component.destroy();
}
}
render() {
return <div ref={element => (this.element = element)} className="panel-height-helper" />;
}
}
import React, { PureComponent } from 'react';
import classNames from 'classnames';
import config, { PanelPlugin } from 'app/core/config';
import _ from 'lodash';
interface Props {
currentType: string;
onTypeChanged: (newType: PanelPlugin) => void;
}
interface State {
pluginList: PanelPlugin[];
}
export class VizTypePicker extends PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
pluginList: this.getPanelPlugins(''),
};
}
getPanelPlugins(filter) {
let panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
.value();
// add sort by sort property
return _.sortBy(panels, 'sort');
}
renderVizPlugin = (plugin, index) => {
const cssClass = classNames({
'viz-picker__item': true,
'viz-picker__item--selected': plugin.id === this.props.currentType,
});
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>
</div>
);
};
render() {
return (
<div className="viz-picker">
<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>
<div className="viz-picker-list">{this.state.pluginList.map(this.renderVizPlugin)}</div>
</div>
);
}
}
...@@ -38,6 +38,8 @@ export class DashNavCtrl { ...@@ -38,6 +38,8 @@ export class DashNavCtrl {
} else if (search.fullscreen) { } else if (search.fullscreen) {
delete search.fullscreen; delete search.fullscreen;
delete search.edit; delete search.edit;
delete search.tab;
delete search.panelId;
} }
this.$location.search(search); this.$location.search(search);
} }
......
...@@ -23,6 +23,9 @@ describe('AddPanelPanel', () => { ...@@ -23,6 +23,9 @@ describe('AddPanelPanel', () => {
hideFromList: false, hideFromList: false,
name: 'Singlestat', name: 'Singlestat',
sort: 2, sort: 2,
module: '',
baseUrl: '',
meta: {},
info: { info: {
logos: { logos: {
small: '', small: '',
...@@ -34,6 +37,9 @@ describe('AddPanelPanel', () => { ...@@ -34,6 +37,9 @@ describe('AddPanelPanel', () => {
hideFromList: true, hideFromList: true,
name: 'Hidden', name: 'Hidden',
sort: 100, sort: 100,
meta: {},
module: '',
baseUrl: '',
info: { info: {
logos: { logos: {
small: '', small: '',
...@@ -45,6 +51,9 @@ describe('AddPanelPanel', () => { ...@@ -45,6 +51,9 @@ describe('AddPanelPanel', () => {
hideFromList: false, hideFromList: false,
name: 'Graph', name: 'Graph',
sort: 1, sort: 1,
meta: {},
module: '',
baseUrl: '',
info: { info: {
logos: { logos: {
small: '', small: '',
...@@ -56,6 +65,9 @@ describe('AddPanelPanel', () => { ...@@ -56,6 +65,9 @@ describe('AddPanelPanel', () => {
hideFromList: false, hideFromList: false,
name: 'Zabbix', name: 'Zabbix',
sort: 100, sort: 100,
meta: {},
module: '',
baseUrl: '',
info: { info: {
logos: { logos: {
small: '', small: '',
...@@ -67,6 +79,9 @@ describe('AddPanelPanel', () => { ...@@ -67,6 +79,9 @@ describe('AddPanelPanel', () => {
hideFromList: false, hideFromList: false,
name: 'Piechart', name: 'Piechart',
sort: 100, sort: 100,
meta: {},
module: '',
baseUrl: '',
info: { info: {
logos: { logos: {
small: '', small: '',
......
...@@ -26,7 +26,7 @@ var panelTemplate = ` ...@@ -26,7 +26,7 @@ var panelTemplate = `
</div> </div>
<div class="panel-full-edit" ng-if="ctrl.panel.isEditing"> <div class="panel-full-edit" ng-if="ctrl.panel.isEditing">
<div class="tabbed-view tabbed-view--panel-edit"> <div class="tabbed-view">
<div class="tabbed-view-header"> <div class="tabbed-view-header">
<h3 class="tabbed-view-panel-title"> <h3 class="tabbed-view-panel-title">
{{ctrl.pluginName}} {{ctrl.pluginName}}
......
...@@ -95,7 +95,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ ...@@ -95,7 +95,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => { PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => {
PanelCtrl.templateUrl = null; PanelCtrl.templateUrl = null;
PanelCtrl.template = `<grafana-panel ctrl="ctrl" class="panel-height-helper">${template}</grafana-panel>`; PanelCtrl.template = `<grafana-panel ctrl="ctrl" class="panel-editor-container">${template}</grafana-panel>`;
return componentInfo; return componentInfo;
}); });
...@@ -110,7 +110,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ ...@@ -110,7 +110,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
let datasource = scope.target.datasource || scope.ctrl.panel.datasource; let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
return datasourceSrv.get(datasource).then(ds => { return datasourceSrv.get(datasource).then(ds => {
scope.datasource = ds; scope.datasource = ds;
console.log('scope', scope);
return importPluginModule(ds.meta.module).then(dsModule => { return importPluginModule(ds.meta.module).then(dsModule => {
return { return {
......
...@@ -138,11 +138,22 @@ const flotDeps = [ ...@@ -138,11 +138,22 @@ const flotDeps = [
'jquery.flot.stackpercent', 'jquery.flot.stackpercent',
'jquery.flot.events', 'jquery.flot.events',
]; ];
for (let flotDep of flotDeps) { for (let flotDep of flotDeps) {
exposeToPlugin(flotDep, { fakeDep: 1 }); exposeToPlugin(flotDep, { fakeDep: 1 });
} }
export function importPluginModule(path: string): Promise<any> { export interface PluginExports {
PanelCtrl?;
any;
PanelComponent?: any;
Datasource?: any;
QueryCtrl?: any;
ConfigCtrl?: any;
AnnotationsQueryCtrl?: any;
}
export function importPluginModule(path: string): Promise<PluginExports> {
let builtIn = builtInPlugins[path]; let builtIn = builtInPlugins[path];
if (builtIn) { if (builtIn) {
return Promise.resolve(builtIn); return Promise.resolve(builtIn);
......
import React from 'react'; import React, { PureComponent } from 'react';
import { PanelProps } from 'app/features/dashboard/dashgrid/DataPanel';
export class ReactTestPanel extends React.Component<any, any> { export class ReactTestPanel extends PureComponent<PanelProps> {
constructor(props) { constructor(props) {
super(props); super(props);
} }
render() { render() {
return <h2>I am a react panel, haha!</h2>; const { data } = this.props;
let value = 0;
if (data.length) {
value = data[0].value;
}
return <h2>I am a react value: {value}</h2>;
} }
} }
......
...@@ -23,8 +23,10 @@ export const ViewStore = types ...@@ -23,8 +23,10 @@ export const ViewStore = types
})) }))
.actions(self => { .actions(self => {
// querystring only // querystring only
function updateQuery(query: any) { function updateQuery(query: any, clear = true) {
if (clear) {
self.query.clear(); self.query.clear();
}
for (let key of Object.keys(query)) { for (let key of Object.keys(query)) {
if (query[key]) { if (query[key]) {
self.query.set(key, query[key]); self.query.set(key, query[key]);
......
...@@ -93,6 +93,7 @@ ...@@ -93,6 +93,7 @@
@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';
// PAGES // PAGES
@import 'pages/login'; @import 'pages/login';
......
...@@ -85,10 +85,6 @@ ...@@ -85,10 +85,6 @@
height: calc(100% - 15px); height: calc(100% - 15px);
} }
.add-panel__item-icon {
padding: 2px;
}
.add-panel__searchbar { .add-panel__searchbar {
width: 100%; width: 100%;
margin-bottom: 10px; margin-bottom: 10px;
......
.tabbed-view { .tabbed-view {
padding: $spacer*3; display: flex;
margin-bottom: $dashboard-padding; flex-direction: column;
height: 100%;
&.tabbed-view--panel-edit { &.tabbed-view--new {
padding: 0;
.tabbed-view-header {
padding: 0px 25px;
background: none;
}
}
&.tabbed-view--panel-edit-new {
padding: 10px 0 0 0; padding: 10px 0 0 0;
height: 100%;
.tabbed-view-header {
padding: 0px;
background: none;
}
} }
} }
.tabbed-view-header { .tabbed-view-header {
background: $page-header-bg;
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;
@include clearfix(); @include clearfix();
...@@ -57,7 +44,10 @@ ...@@ -57,7 +44,10 @@
} }
.tabbed-view-body { .tabbed-view-body {
padding: $spacer*2 $spacer; padding: $spacer*2 $spacer $spacer $spacer;
display: flex;
flex-direction: column;
flex: 1;
&--small { &--small {
min-height: 0px; min-height: 0px;
......
.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-list {
padding-top: $spacer;
display: flex;
flex-direction: column;
overflow: hidden;
flex-grow: 1;
}
.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;
&:hover {
background: $card-background-hover;
}
&--selected {
border: 1px solid $orange;
.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%);
}
.dashboard-container { .dashboard-container {
padding: $dashboard-padding; padding: $dashboard-padding;
width: 100%; width: 100%;
min-height: 100%; height: 100%;
box-sizing: border-box;
} }
.template-variable { .template-variable {
...@@ -28,12 +29,17 @@ div.flot-text { ...@@ -28,12 +29,17 @@ div.flot-text {
height: 100%; height: 100%;
} }
.panel-editor-container {
display: flex;
flex-direction: column;
height: 100%;
}
.panel-container { .panel-container {
background-color: $panel-bg; background-color: $panel-bg;
border: $panel-border; border: $panel-border;
position: relative; position: relative;
border-radius: 3px; border-radius: 3px;
height: 100%;
&.panel-transparent { &.panel-transparent {
background-color: transparent; background-color: transparent;
...@@ -233,5 +239,5 @@ div.flot-text { ...@@ -233,5 +239,5 @@ div.flot-text {
} }
.panel-full-edit { .panel-full-edit {
margin: $dashboard-padding (-$dashboard-padding) 0 (-$dashboard-padding); padding-top: $dashboard-padding;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment