mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: add panel inspector (#20052)
This commit is contained in:
@@ -13,6 +13,7 @@ export interface BuildInfo {
|
|||||||
|
|
||||||
interface FeatureToggles {
|
interface FeatureToggles {
|
||||||
transformations: boolean;
|
transformations: boolean;
|
||||||
|
inspect: boolean;
|
||||||
expressions: boolean;
|
expressions: boolean;
|
||||||
}
|
}
|
||||||
export class GrafanaBootConfig {
|
export class GrafanaBootConfig {
|
||||||
@@ -48,6 +49,7 @@ export class GrafanaBootConfig {
|
|||||||
pluginsToPreload: string[] = [];
|
pluginsToPreload: string[] = [];
|
||||||
featureToggles: FeatureToggles = {
|
featureToggles: FeatureToggles = {
|
||||||
transformations: false,
|
transformations: false,
|
||||||
|
inspect: false,
|
||||||
expressions: false,
|
expressions: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'mousetrap-global-bind';
|
|||||||
import { ContextSrv } from './context_srv';
|
import { ContextSrv } from './context_srv';
|
||||||
import { ILocationService, ITimeoutService, IRootScopeService } from 'angular';
|
import { ILocationService, ITimeoutService, IRootScopeService } from 'angular';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export class KeybindingSrv {
|
export class KeybindingSrv {
|
||||||
helpModal: boolean;
|
helpModal: boolean;
|
||||||
@@ -264,6 +265,13 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// inspect panel
|
||||||
|
this.bind('p i', () => {
|
||||||
|
if (dashboard.meta.focusPanelId) {
|
||||||
|
getLocationSrv().update({ partial: true, query: { inspect: dashboard.meta.focusPanelId } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// toggle panel legend
|
// toggle panel legend
|
||||||
this.bind('p l', () => {
|
this.bind('p l', () => {
|
||||||
if (dashboard.meta.focusPanelId) {
|
if (dashboard.meta.focusPanelId) {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Libraries
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
|
import { JSONFormatter, Modal } from '@grafana/ui';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dashboard: DashboardModel;
|
||||||
|
panel: PanelModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {}
|
||||||
|
|
||||||
|
export class PanelInspector extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDismiss = () => {
|
||||||
|
getLocationSrv().update({
|
||||||
|
query: { inspect: null },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { panel } = this.props;
|
||||||
|
if (!panel) {
|
||||||
|
this.onDismiss(); // Try to close the component
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const bodyStyle = css`
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// TODO? should we get the result with an observable once?
|
||||||
|
const data = (panel.getQueryRunner() as any).lastResult;
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<div className="modal-header-title">
|
||||||
|
<i className="fa fa-info-circle" />
|
||||||
|
<span className="p-l-1">{panel.title ? panel.title : 'Panel'}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onDismiss={this.onDismiss}
|
||||||
|
isOpen={true}
|
||||||
|
>
|
||||||
|
<div className={bodyStyle}>
|
||||||
|
<JSONFormatter json={data} open={2} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
AppNotificationSeverity,
|
AppNotificationSeverity,
|
||||||
} from 'app/types';
|
} from 'app/types';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
|
import { PanelInspector } from '../components/Inspector/PanelInspector';
|
||||||
export interface Props {
|
export interface Props {
|
||||||
urlUid?: string;
|
urlUid?: string;
|
||||||
urlSlug?: string;
|
urlSlug?: string;
|
||||||
@@ -38,6 +39,7 @@ export interface Props {
|
|||||||
editview?: string;
|
editview?: string;
|
||||||
urlPanelId?: string;
|
urlPanelId?: string;
|
||||||
urlFolderId?: string;
|
urlFolderId?: string;
|
||||||
|
inspectPanelId?: string;
|
||||||
$scope: any;
|
$scope: any;
|
||||||
$injector: any;
|
$injector: any;
|
||||||
routeInfo: DashboardRouteInfo;
|
routeInfo: DashboardRouteInfo;
|
||||||
@@ -250,7 +252,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, editview, $injector, isInitSlow, initError } = this.props;
|
const { dashboard, editview, $injector, isInitSlow, initError, inspectPanelId } = this.props;
|
||||||
const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state;
|
const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state;
|
||||||
|
|
||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
@@ -270,6 +272,9 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
|
'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Find the panel to inspect
|
||||||
|
const inspectPanel = inspectPanelId ? dashboard.getPanelById(parseInt(inspectPanelId, 10)) : null;
|
||||||
|
|
||||||
// Only trigger render when the scroll has moved by 25
|
// Only trigger render when the scroll has moved by 25
|
||||||
const approximateScrollTop = Math.round(scrollTop / 25) * 25;
|
const approximateScrollTop = Math.round(scrollTop / 25) * 25;
|
||||||
|
|
||||||
@@ -306,6 +311,8 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{inspectPanel && <PanelInspector dashboard={dashboard} panel={inspectPanel} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -320,6 +327,7 @@ export const mapStateToProps = (state: StoreState) => ({
|
|||||||
urlFolderId: state.location.query.folderId,
|
urlFolderId: state.location.query.folderId,
|
||||||
urlFullscreen: !!state.location.query.fullscreen,
|
urlFullscreen: !!state.location.query.fullscreen,
|
||||||
urlEdit: !!state.location.query.edit,
|
urlEdit: !!state.location.query.edit,
|
||||||
|
inspectPanelId: state.location.query.inspect,
|
||||||
initPhase: state.dashboard.initPhase,
|
initPhase: state.dashboard.initPhase,
|
||||||
isInitSlow: state.dashboard.isInitSlow,
|
isInitSlow: state.dashboard.isInitSlow,
|
||||||
initError: state.dashboard.initError,
|
initError: state.dashboard.initError,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
import { store } from 'app/store/store';
|
import { store } from 'app/store/store';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
|
||||||
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
||||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
import { PanelMenuItem } from '@grafana/data';
|
import { PanelMenuItem } from '@grafana/data';
|
||||||
|
|
||||||
export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||||
@@ -37,6 +39,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
|||||||
sharePanel(dashboard, panel);
|
sharePanel(dashboard, panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onInspectPanel = () => {
|
||||||
|
getLocationSrv().update({
|
||||||
|
partial: true,
|
||||||
|
query: {
|
||||||
|
inspect: panel.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onDuplicatePanel = () => {
|
const onDuplicatePanel = () => {
|
||||||
duplicatePanel(dashboard, panel);
|
duplicatePanel(dashboard, panel);
|
||||||
};
|
};
|
||||||
@@ -78,6 +89,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
|||||||
shortcut: 'p s',
|
shortcut: 'p s',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (config.featureToggles.inspect) {
|
||||||
|
menu.push({
|
||||||
|
text: 'Inspect',
|
||||||
|
iconClassName: 'fa fa-fw fa-info-circle',
|
||||||
|
onClick: onInspectPanel,
|
||||||
|
shortcut: 'p i',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const subMenu: PanelMenuItem[] = [];
|
const subMenu: PanelMenuItem[] = [];
|
||||||
|
|
||||||
if (!panel.fullscreen && dashboard.meta.canEdit) {
|
if (!panel.fullscreen && dashboard.meta.canEdit) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { auto } from 'angular';
|
|||||||
import { TemplateSrv } from '../templating/template_srv';
|
import { TemplateSrv } from '../templating/template_srv';
|
||||||
import { getPanelLinksSupplier } from './panellinks/linkSuppliers';
|
import { getPanelLinksSupplier } from './panellinks/linkSuppliers';
|
||||||
import { renderMarkdown, AppEvent, PanelEvents, PanelPluginMeta } from '@grafana/data';
|
import { renderMarkdown, AppEvent, PanelEvents, PanelPluginMeta } from '@grafana/data';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export class PanelCtrl {
|
export class PanelCtrl {
|
||||||
panel: any;
|
panel: any;
|
||||||
@@ -145,6 +146,15 @@ export class PanelCtrl {
|
|||||||
shortcut: 'p s',
|
shortcut: 'p s',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (config.featureToggles.inspect) {
|
||||||
|
menu.push({
|
||||||
|
text: 'Inspect',
|
||||||
|
icon: 'fa fa-fw fa-info-circle',
|
||||||
|
click: 'ctrl.inspectPanel();',
|
||||||
|
shortcut: 'p i',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Additional items from sub-class
|
// Additional items from sub-class
|
||||||
menu.push(...(await this.getAdditionalMenuItems()));
|
menu.push(...(await this.getAdditionalMenuItems()));
|
||||||
|
|
||||||
@@ -234,6 +244,15 @@ export class PanelCtrl {
|
|||||||
sharePanelUtil(this.dashboard, this.panel);
|
sharePanelUtil(this.dashboard, this.panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inspectPanel() {
|
||||||
|
getLocationSrv().update({
|
||||||
|
query: {
|
||||||
|
inspect: this.panel.id,
|
||||||
|
},
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getInfoMode() {
|
getInfoMode() {
|
||||||
if (this.error) {
|
if (this.error) {
|
||||||
return 'error';
|
return 'error';
|
||||||
|
|||||||
Reference in New Issue
Block a user