The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/dashboard/dashgrid/DashboardGrid.tsx

205 lines
5.5 KiB

import React from 'react';
import ReactGridLayout from 'react-grid-layout';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { DashboardPanel } from './DashboardPanel';
import { DashboardModel } from '../dashboard_model';
import { PanelModel } from '../panel_model';
import classNames from 'classnames';
import sizeMe from 'react-sizeme';
let lastGridWidth = 1200;
function GridWrapper({
size,
layout,
onLayoutChange,
children,
onDragStop,
onResize,
onResizeStop,
onWidthChange,
className,
isResizable,
isDraggable,
isFullscreen,
}) {
const width = size.width > 0 ? size.width : lastGridWidth;
if (width !== lastGridWidth) {
if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
onWidthChange();
lastGridWidth = width;
}
}
return (
<ReactGridLayout
width={lastGridWidth}
className={className}
isDraggable={isDraggable}
isResizable={isResizable}
measureBeforeMount={false}
containerPadding={[0, 0]}
useCSSTransforms={true}
margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
cols={GRID_COLUMN_COUNT}
rowHeight={GRID_CELL_HEIGHT}
draggableHandle=".grid-drag-handle"
layout={layout}
onResize={onResize}
onResizeStop={onResizeStop}
onDragStop={onDragStop}
onLayoutChange={onLayoutChange}
>
{children}
</ReactGridLayout>
);
}
const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
export interface DashboardGridProps {
dashboard: DashboardModel;
}
export class DashboardGrid extends React.Component<DashboardGridProps, any> {
gridToPanelMap: any;
panelMap: { [id: string]: PanelModel };
constructor(props) {
super(props);
this.onLayoutChange = this.onLayoutChange.bind(this);
this.onResize = this.onResize.bind(this);
this.onResizeStop = this.onResizeStop.bind(this);
this.onDragStop = this.onDragStop.bind(this);
this.onWidthChange = this.onWidthChange.bind(this);
this.state = { animated: false };
// subscribe to dashboard events
const dashboard = this.props.dashboard;
dashboard.on('panel-added', this.triggerForceUpdate.bind(this));
dashboard.on('panel-removed', this.triggerForceUpdate.bind(this));
dashboard.on('repeats-processed', this.triggerForceUpdate.bind(this));
dashboard.on('view-mode-changed', this.onViewModeChanged.bind(this));
dashboard.on('row-collapsed', this.triggerForceUpdate.bind(this));
dashboard.on('row-expanded', this.triggerForceUpdate.bind(this));
dashboard.on('panel-type-changed', this.triggerForceUpdate.bind(this));
}
buildLayout() {
const layout = [];
this.panelMap = {};
for (const panel of this.props.dashboard.panels) {
const stringId = panel.id.toString();
this.panelMap[stringId] = panel;
if (!panel.gridPos) {
console.log('panel without gridpos');
continue;
}
const panelPos: any = {
i: stringId,
x: panel.gridPos.x,
y: panel.gridPos.y,
w: panel.gridPos.w,
h: panel.gridPos.h,
};
if (panel.type === 'row') {
panelPos.w = GRID_COLUMN_COUNT;
panelPos.h = 1;
panelPos.isResizable = false;
panelPos.isDraggable = panel.collapsed;
}
layout.push(panelPos);
}
return layout;
}
onLayoutChange(newLayout) {
for (const newPos of newLayout) {
this.panelMap[newPos.i].updateGridPos(newPos);
}
this.props.dashboard.sortPanelsByGridPos();
}
triggerForceUpdate() {
this.forceUpdate();
}
onWidthChange() {
for (const panel of this.props.dashboard.panels) {
panel.resizeDone();
}
}
onViewModeChanged(payload) {
this.setState({ animated: !payload.fullscreen });
}
updateGridPos(item, layout) {
this.panelMap[item.i].updateGridPos(item);
// react-grid-layout has a bug (#670), and onLayoutChange() is only called when the component is mounted.
// So it's required to call it explicitly when panel resized or moved to save layout changes.
this.onLayoutChange(layout);
}
onResize(layout, oldItem, newItem) {
this.panelMap[newItem.i].updateGridPos(newItem);
}
onResizeStop(layout, oldItem, newItem) {
this.updateGridPos(newItem, layout);
this.panelMap[newItem.i].resizeDone();
}
onDragStop(layout, oldItem, newItem) {
this.updateGridPos(newItem, layout);
}
componentDidMount() {
setTimeout(() => {
this.setState({ animated: true });
});
}
renderPanels() {
const panelElements = [];
for (const panel of this.props.dashboard.panels) {
const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen });
panelElements.push(
<div key={panel.id.toString()} className={panelClasses} id={`panel-${panel.id}`}>
<DashboardPanel panel={panel} dashboard={this.props.dashboard} panelType={panel.type} />
</div>
);
}
return panelElements;
}
render() {
return (
<SizedReactLayoutGrid
className={classNames({ layout: true, animated: this.state.animated })}
layout={this.buildLayout()}
isResizable={this.props.dashboard.meta.canEdit}
isDraggable={this.props.dashboard.meta.canEdit}
onLayoutChange={this.onLayoutChange}
onWidthChange={this.onWidthChange}
onDragStop={this.onDragStop}
onResize={this.onResize}
onResizeStop={this.onResizeStop}
isFullscreen={this.props.dashboard.meta.fullscreen}
>
{this.renderPanels()}
</SizedReactLayoutGrid>
);
}
}