mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'react-panels' of github.com:grafana/grafana into react-panels
This commit is contained in:
commit
e052e165e9
@ -7,9 +7,20 @@ export interface BuildInfo {
|
||||
env: string;
|
||||
}
|
||||
|
||||
export interface PanelPlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
meta: any;
|
||||
hideFromList: boolean;
|
||||
module: string;
|
||||
baseUrl: string;
|
||||
info: any;
|
||||
sort: number;
|
||||
}
|
||||
|
||||
export class Settings {
|
||||
datasources: any;
|
||||
panels: any;
|
||||
panels: PanelPlugin[];
|
||||
appSubUrl: string;
|
||||
window_title_prefix: string;
|
||||
buildInfo: BuildInfo;
|
||||
|
@ -139,7 +139,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
}
|
||||
|
||||
onViewModeChanged(payload) {
|
||||
this.setState({ animated: payload.fullscreen });
|
||||
this.setState({ animated: !payload.fullscreen });
|
||||
}
|
||||
|
||||
updateGridPos(item, layout) {
|
||||
|
@ -5,24 +5,27 @@ import { DashboardModel } from '../dashboard_model';
|
||||
import { getAngularLoader, AngularComponent } from 'app/core/services/angular_loader';
|
||||
import { DashboardRow } from './DashboardRow';
|
||||
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';
|
||||
|
||||
export interface DashboardPanelProps {
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
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;
|
||||
angularPanel: AngularComponent;
|
||||
pluginInfo: any;
|
||||
pluginExports: any;
|
||||
specialPanels = {};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state = { pluginExports: null };
|
||||
|
||||
this.specialPanels['row'] = this.renderRow.bind(this);
|
||||
this.specialPanels['add-panel'] = this.renderAddPanel.bind(this);
|
||||
@ -32,8 +35,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
|
||||
|
||||
// load panel plugin
|
||||
importPluginModule(this.pluginInfo.module).then(pluginExports => {
|
||||
this.pluginExports = pluginExports;
|
||||
this.forceUpdate();
|
||||
this.setState({ pluginExports: pluginExports });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -51,8 +53,7 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// skip loading angular component if we have no element
|
||||
// or we have already loaded it
|
||||
// skip loading angular component if we have no element or we have already loaded it
|
||||
if (!this.element || this.angularPanel) {
|
||||
return;
|
||||
}
|
||||
@ -70,18 +71,20 @@ export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { pluginExports } = this.state;
|
||||
|
||||
if (this.isSpecial()) {
|
||||
return this.specialPanels[this.props.panel.type]();
|
||||
}
|
||||
|
||||
if (!this.pluginExports) {
|
||||
if (!pluginExports) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.pluginExports.PanelComponent) {
|
||||
if (pluginExports.PanelComponent) {
|
||||
return (
|
||||
<PanelChrome
|
||||
component={this.pluginExports.PanelComponent}
|
||||
component={pluginExports.PanelComponent}
|
||||
panel={this.props.panel}
|
||||
dashboard={this.props.dashboard}
|
||||
/>
|
||||
|
85
public/app/features/dashboard/dashgrid/DataPanel.tsx
Normal file
85
public/app/features/dashboard/dashgrid/DataPanel.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
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;
|
||||
};
|
||||
|
@ -1,44 +1,58 @@
|
||||
import React from 'react';
|
||||
import React, { ComponentClass } from 'react';
|
||||
import $ from 'jquery';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||
import { PanelHeader } from './PanelHeader';
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
import { DataPanel, PanelProps, DataPanelWrapper } from './DataPanel';
|
||||
|
||||
const TITLE_HEIGHT = 27;
|
||||
const PANEL_BORDER = 2;
|
||||
|
||||
export interface PanelChromeProps {
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
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) {
|
||||
super(props);
|
||||
|
||||
this.props.panel.events.on('panel-size-changed', this.triggerForceUpdate.bind(this));
|
||||
}
|
||||
|
||||
triggerForceUpdate() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
let panelContentStyle = {
|
||||
this.state = {
|
||||
height: this.getPanelHeight(),
|
||||
};
|
||||
|
||||
let PanelComponent = this.props.component;
|
||||
this.panelComponent = DataPanelWrapper(this.props.component);
|
||||
this.props.panel.events.on('panel-size-changed', this.onPanelSizeChanged);
|
||||
}
|
||||
|
||||
onPanelSizeChanged = () => {
|
||||
this.setState({
|
||||
height: this.getPanelHeight(),
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
console.log('panel chrome mounted');
|
||||
}
|
||||
|
||||
render() {
|
||||
let PanelComponent = this.panelComponent;
|
||||
|
||||
return (
|
||||
<div className="panel-height-helper">
|
||||
<div className="panel-editor-container">
|
||||
<div className="panel-container">
|
||||
<PanelHeader panel={this.props.panel} dashboard={this.props.dashboard} />
|
||||
<div className="panel-content" style={panelContentStyle}>
|
||||
{<PanelComponent />}
|
||||
<div className="panel-content" style={{ height: this.state.height }}>
|
||||
{<PanelComponent type={'test'} queries={[]} isVisible={true} />}
|
||||
</div>
|
||||
</div>
|
||||
{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> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { PanelModel } from '../panel_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 {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
interface PanelEditorTab {
|
||||
id: string;
|
||||
text: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class PanelEditor extends React.Component<PanelEditorProps, any> {
|
||||
queryElement: any;
|
||||
queryComp: AngularComponent;
|
||||
tabs: any[];
|
||||
tabs: PanelEditorTab[];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -22,40 +32,42 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
|
||||
];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.queryElement) {
|
||||
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.queryComp = loader.load(this.queryElement, scopeProps, template);
|
||||
renderQueriesTab() {
|
||||
return <QueriesTab panel={this.props.panel} dashboard={this.props.dashboard} />;
|
||||
}
|
||||
|
||||
onChangeTab = tabName => {};
|
||||
renderVizTab() {
|
||||
return (
|
||||
<div className="viz-editor">
|
||||
<div className="viz-editor-col1">
|
||||
<VizTypePicker currentType={this.props.panel.type} onTypeChanged={this.onVizTypeChanged} />
|
||||
</div>
|
||||
<div className="viz-editor-col2">
|
||||
<h5 className="page-heading">Options</h5>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onVizTypeChanged = (plugin: PanelPlugin) => {
|
||||
this.props.panel.type = plugin.id;
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onChangeTab = (tab: PanelEditorTab) => {
|
||||
store.view.updateQuery({ tab: tab.id }, false);
|
||||
};
|
||||
|
||||
render() {
|
||||
const activeTab: string = store.view.query.get('tab') || 'queries';
|
||||
|
||||
return (
|
||||
<div className="tabbed-view tabbed-view--panel-edit-new">
|
||||
<div className="tabbed-view tabbed-view--new">
|
||||
<div className="tabbed-view-header">
|
||||
<ul className="gf-tabs">
|
||||
<li className="gf-tabs-item">
|
||||
<a className="gf-tabs-link active">Queries</a>
|
||||
</li>
|
||||
<li className="gf-tabs-item">
|
||||
<a className="gf-tabs-link">Visualization</a>
|
||||
</li>
|
||||
{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();">
|
||||
@ -64,9 +76,32 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
export class PanelHeader extends React.Component<PanelHeaderProps, any> {
|
||||
onEditPanel = () => {
|
||||
store.view.updateQuery({
|
||||
panelId: this.props.panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
});
|
||||
store.view.updateQuery(
|
||||
{
|
||||
panelId: this.props.panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
49
public/app/features/dashboard/dashgrid/QueriesTab.tsx
Normal file
49
public/app/features/dashboard/dashgrid/QueriesTab.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
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" />;
|
||||
}
|
||||
}
|
61
public/app/features/dashboard/dashgrid/VizTypePicker.tsx
Normal file
61
public/app/features/dashboard/dashgrid/VizTypePicker.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
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 {
|
||||
} else if (search.fullscreen) {
|
||||
delete search.fullscreen;
|
||||
delete search.edit;
|
||||
delete search.tab;
|
||||
delete search.panelId;
|
||||
}
|
||||
this.$location.search(search);
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ describe('AddPanelPanel', () => {
|
||||
hideFromList: false,
|
||||
name: 'Singlestat',
|
||||
sort: 2,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
meta: {},
|
||||
info: {
|
||||
logos: {
|
||||
small: '',
|
||||
@ -34,6 +37,9 @@ describe('AddPanelPanel', () => {
|
||||
hideFromList: true,
|
||||
name: 'Hidden',
|
||||
sort: 100,
|
||||
meta: {},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
info: {
|
||||
logos: {
|
||||
small: '',
|
||||
@ -45,6 +51,9 @@ describe('AddPanelPanel', () => {
|
||||
hideFromList: false,
|
||||
name: 'Graph',
|
||||
sort: 1,
|
||||
meta: {},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
info: {
|
||||
logos: {
|
||||
small: '',
|
||||
@ -56,6 +65,9 @@ describe('AddPanelPanel', () => {
|
||||
hideFromList: false,
|
||||
name: 'Zabbix',
|
||||
sort: 100,
|
||||
meta: {},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
info: {
|
||||
logos: {
|
||||
small: '',
|
||||
@ -67,6 +79,9 @@ describe('AddPanelPanel', () => {
|
||||
hideFromList: false,
|
||||
name: 'Piechart',
|
||||
sort: 100,
|
||||
meta: {},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
info: {
|
||||
logos: {
|
||||
small: '',
|
||||
|
@ -26,7 +26,7 @@ var panelTemplate = `
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<h3 class="tabbed-view-panel-title">
|
||||
{{ctrl.pluginName}}
|
||||
|
@ -95,7 +95,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
|
||||
PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => {
|
||||
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;
|
||||
});
|
||||
|
||||
@ -110,7 +110,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
|
||||
return datasourceSrv.get(datasource).then(ds => {
|
||||
scope.datasource = ds;
|
||||
console.log('scope', scope);
|
||||
|
||||
return importPluginModule(ds.meta.module).then(dsModule => {
|
||||
return {
|
||||
|
@ -138,11 +138,22 @@ const flotDeps = [
|
||||
'jquery.flot.stackpercent',
|
||||
'jquery.flot.events',
|
||||
];
|
||||
|
||||
for (let flotDep of flotDeps) {
|
||||
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];
|
||||
if (builtIn) {
|
||||
return Promise.resolve(builtIn);
|
||||
|
@ -1,12 +1,20 @@
|
||||
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) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
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
|
||||
}))
|
||||
.actions(self => {
|
||||
// querystring only
|
||||
function updateQuery(query: any) {
|
||||
self.query.clear();
|
||||
function updateQuery(query: any, clear = true) {
|
||||
if (clear) {
|
||||
self.query.clear();
|
||||
}
|
||||
for (let key of Object.keys(query)) {
|
||||
if (query[key]) {
|
||||
self.query.set(key, query[key]);
|
||||
|
@ -93,6 +93,7 @@
|
||||
@import 'components/form_select_box';
|
||||
@import 'components/user-picker';
|
||||
@import 'components/description-picker';
|
||||
@import 'components/viz_editor';
|
||||
|
||||
// PAGES
|
||||
@import 'pages/login';
|
||||
|
@ -85,10 +85,6 @@
|
||||
height: calc(100% - 15px);
|
||||
}
|
||||
|
||||
.add-panel__item-icon {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.add-panel__searchbar {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
|
@ -1,28 +1,15 @@
|
||||
.tabbed-view {
|
||||
padding: $spacer*3;
|
||||
margin-bottom: $dashboard-padding;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
&.tabbed-view--panel-edit {
|
||||
padding: 0;
|
||||
|
||||
.tabbed-view-header {
|
||||
padding: 0px 25px;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.tabbed-view--panel-edit-new {
|
||||
&.tabbed-view--new {
|
||||
padding: 10px 0 0 0;
|
||||
|
||||
.tabbed-view-header {
|
||||
padding: 0px;
|
||||
background: none;
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tabbed-view-header {
|
||||
background: $page-header-bg;
|
||||
box-shadow: $page-header-shadow;
|
||||
border-bottom: 1px solid $page-header-border-color;
|
||||
@include clearfix();
|
||||
@ -57,7 +44,10 @@
|
||||
}
|
||||
|
||||
.tabbed-view-body {
|
||||
padding: $spacer*2 $spacer;
|
||||
padding: $spacer*2 $spacer $spacer $spacer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
&--small {
|
||||
min-height: 0px;
|
||||
|
78
public/sass/components/_viz_editor.scss
Normal file
78
public/sass/components/_viz_editor.scss
Normal file
@ -0,0 +1,78 @@
|
||||
.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%);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
.dashboard-container {
|
||||
padding: $dashboard-padding;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.template-variable {
|
||||
@ -28,12 +29,17 @@ div.flot-text {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
background-color: $panel-bg;
|
||||
border: $panel-border;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
height: 100%;
|
||||
|
||||
&.panel-transparent {
|
||||
background-color: transparent;
|
||||
@ -233,5 +239,5 @@ div.flot-text {
|
||||
}
|
||||
|
||||
.panel-full-edit {
|
||||
margin: $dashboard-padding (-$dashboard-padding) 0 (-$dashboard-padding);
|
||||
padding-top: $dashboard-padding;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user