2025-04-01 10:38:02 +09:00

270 lines
7.1 KiB
TypeScript

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();
}
}