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-scene/scene/keyboardShortcuts.ts

269 lines
7.1 KiB

import { locationUtil, SetPanelAttentionEvent } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { sceneGraph, VizPanel } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { KeybindingSet } from 'app/core/services/KeybindingSet';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction } from 'app/types';
import { shareDashboardType } from '../../dashboard/components/ShareModal/utils';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getPanelIdForVizPanel } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
import { onRemovePanel, toggleVizPanelLegend } from './PanelMenuBehavior';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
export function setupKeyboardShortcuts(scene: DashboardScene) {
const keybindings = new KeybindingSet();
let vizPanelKey: string | null = null;
const canEdit = scene.canEditDashboard();
const panelAttentionSubscription = appEvents.subscribe(SetPanelAttentionEvent, (event) => {
if (typeof event.payload.panelId === 'string') {
vizPanelKey = event.payload.panelId;
}
});
function withFocusedPanel(scene: DashboardScene, fn: (vizPanel: VizPanel) => void) {
return () => {
const vizPanel = sceneGraph.findObject(scene, (o) => o.state.key === vizPanelKey);
if (vizPanel && vizPanel instanceof VizPanel) {
fn(vizPanel);
return;
}
};
}
// View panel
keybindings.addBinding({
key: 'v',
onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
if (!scene.state.viewPanelScene) {
const url = locationUtil.stripBaseFromUrl(getViewPanelUrl(vizPanel));
locationService.push(url);
}
}),
});
// Panel share
if (config.featureToggles.newDashboardSharingComponent) {
keybindings.addBinding({
key: 'p u',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.link,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
keybindings.addBinding({
key: 'p e',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.embed,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
if (
contextSrv.isSignedIn &&
config.snapshotEnabled &&
contextSrv.hasPermission(AccessControlAction.SnapshotsCreate)
) {
keybindings.addBinding({
key: 'p s',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const drawer = new ShareDrawer({
shareView: shareDashboardType.snapshot,
panelRef: vizPanel.getRef(),
});
scene.showModal(drawer);
}),
});
}
} else {
keybindings.addBinding({
key: 'p s',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
scene.showModal(new ShareModal({ panelRef: vizPanel.getRef() }));
}),
});
}
// Panel inspect
keybindings.addBinding({
key: 'i',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
locationService.push(locationUtil.stripBaseFromUrl(getInspectUrl(vizPanel)));
}),
});
// Got to Explore for panel
keybindings.addBinding({
key: 'p x',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const url = await tryGetExploreUrlForPanel(vizPanel);
if (url) {
locationService.push(url);
}
}),
});
// Toggle legend
keybindings.addBinding({
key: 'p l',
onTrigger: withFocusedPanel(scene, toggleVizPanelLegend),
});
// Refresh
keybindings.addBinding({
key: 'd r',
onTrigger: () => sceneGraph.getTimeRange(scene).onRefresh(),
});
// Zoom out
keybindings.addBinding({
key: 't z',
onTrigger: () => {
handleZoomOut(scene);
},
});
keybindings.addBinding({
key: 'ctrl+z',
onTrigger: () => {
handleZoomOut(scene);
},
});
// Relative -> Absolute time range
keybindings.addBinding({
key: 't a',
onTrigger: () => {
const timePicker = dashboardSceneGraph.getTimePicker(scene);
timePicker?.toAbsolute();
},
});
keybindings.addBinding({
key: 't left',
onTrigger: () => {
handleTimeRangeShift(scene, 'left');
},
});
keybindings.addBinding({
key: 't right',
onTrigger: () => {
handleTimeRangeShift(scene, 'right');
},
});
if (canEdit) {
// Panel edit
keybindings.addBinding({
key: 'e',
onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => {
const sceneRoot = vizPanel.getRoot();
if (sceneRoot instanceof DashboardScene) {
const panelId = getPanelIdForVizPanel(vizPanel);
if (!scene.state.editPanel) {
const url = locationUtil.stripBaseFromUrl(getEditPanelUrl(panelId));
locationService.push(url);
}
}
}),
});
// Dashboard settings
keybindings.addBinding({
key: 'd s',
onTrigger: scene.onOpenSettings,
});
// Open save drawer
keybindings.addBinding({
key: 'mod+s',
onTrigger: () => scene.openSaveDrawer({}),
});
// delete panel
keybindings.addBinding({
key: 'p r',
onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
if (scene.state.isEditing) {
onRemovePanel(scene, vizPanel);
}
}),
});
// duplicate panel
keybindings.addBinding({
key: 'p d',
onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
if (scene.state.isEditing) {
scene.duplicatePanel(vizPanel);
}
}),
});
// collapse all rows
keybindings.addBinding({
key: 'd shift+c',
onTrigger: () => {
if (scene.state.body instanceof DefaultGridLayoutManager) {
scene.state.body.collapseAllRows();
}
},
});
// expand all rows
keybindings.addBinding({
key: 'd shift+e',
onTrigger: () => {
if (scene.state.body instanceof DefaultGridLayoutManager) {
scene.state.body.expandAllRows();
}
},
});
}
// toggle all panel legends (TODO)
// toggle all exemplars (TODO)
return () => {
keybindings.removeAll();
panelAttentionSubscription.unsubscribe();
};
}
function handleZoomOut(scene: DashboardScene) {
const timePicker = dashboardSceneGraph.getTimePicker(scene);
timePicker?.onZoom();
}
function handleTimeRangeShift(scene: DashboardScene, direction: 'left' | 'right') {
const timePicker = dashboardSceneGraph.getTimePicker(scene);
if (!timePicker) {
return;
}
if (direction === 'left') {
timePicker.onMoveBackward();
}
if (direction === 'right') {
timePicker.onMoveForward();
}
}