Submit
Path:
~
/
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
dashboard
/
components
/
PanelEditor
/
File Content:
PanelEditor.tsx
import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; import { Subscription } from 'rxjs'; import { FieldConfigSource, GrafanaTheme2, NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; import { Button, HorizontalGroup, InlineSwitch, ModalsController, RadioButtonGroup, stylesFactory, Themeable2, ToolbarButton, ToolbarButtonRow, withTheme2, Stack, } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { Page } from 'app/core/components/Page/Page'; import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import { appEvents } from 'app/core/core'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal'; import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; import { StoreState } from 'app/types'; import { PanelOptionsChangedEvent, ShowModalReactEvent } from 'app/types/events'; import { notifyApp } from '../../../../core/actions'; import { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal'; import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; import { getVariablesByKey } from '../../../variables/state/selectors'; import { DashboardPanel } from '../../dashgrid/DashboardPanel'; import { DashboardModel, PanelModel } from '../../state'; import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; import { SaveDashboardDrawer } from '../SaveDashboard/SaveDashboardDrawer'; import { OptionsPane } from './OptionsPane'; import { PanelEditorTableView } from './PanelEditorTableView'; import { PanelEditorTabs } from './PanelEditorTabs'; import { VisualizationButton } from './VisualizationButton'; import { discardPanelChanges, initPanelEditor, updatePanelEditorUIState } from './state/actions'; import { PanelEditorUIState, toggleTableView } from './state/reducers'; import { getPanelEditorTabs } from './state/selectors'; import { DisplayMode, displayModes, PanelEditorTab } from './types'; import { calculatePanelSize } from './utils'; interface OwnProps { dashboard: DashboardModel; sourcePanel: PanelModel; sectionNav: NavModel; pageNav: NavModelItem; className?: string; tab?: string; } const mapStateToProps = (state: StoreState, ownProps: OwnProps) => { const panel = state.panelEditor.getPanel(); const panelState = getPanelStateForModel(state, panel); return { panel, plugin: panelState?.plugin, instanceState: panelState?.instanceState, initDone: state.panelEditor.initDone, uiState: state.panelEditor.ui, tableViewEnabled: state.panelEditor.tableViewEnabled, variables: getVariablesByKey(ownProps.dashboard.uid, state), }; }; const mapDispatchToProps = { initPanelEditor, discardPanelChanges, updatePanelEditorUIState, updateTimeZoneForSession, toggleTableView, notifyApp, }; const connector = connect(mapStateToProps, mapDispatchToProps); type Props = OwnProps & ConnectedProps<typeof connector> & Themeable2; interface State { showSaveLibraryPanelModal?: boolean; } export class PanelEditorUnconnected extends PureComponent<Props> { private eventSubs?: Subscription; state: State = { showSaveLibraryPanelModal: false, }; componentDidMount() { this.props.initPanelEditor(this.props.sourcePanel, this.props.dashboard); } componentDidUpdate() { const { panel, initDone } = this.props; if (initDone && !this.eventSubs) { this.eventSubs = new Subscription(); this.eventSubs.add(panel.events.subscribe(PanelOptionsChangedEvent, this.triggerForceUpdate)); } } componentWillUnmount() { // redux action exitPanelEditor is called on location change from DashboardPrompt this.eventSubs?.unsubscribe(); } triggerForceUpdate = () => { this.forceUpdate(); }; onBack = () => { locationService.partial({ editPanel: null, tab: null, showCategory: null, }); }; onDiscard = () => { this.props.discardPanelChanges(); this.onBack(); }; onSaveDashboard = () => { appEvents.publish( new ShowModalReactEvent({ component: SaveDashboardDrawer, props: { dashboard: this.props.dashboard }, }) ); }; onSaveLibraryPanel = async () => { if (!isPanelModelLibraryPanel(this.props.panel)) { // New library panel, no need to display modal return; } this.setState({ showSaveLibraryPanelModal: true }); }; onChangeTab = (tab: PanelEditorTab) => { locationService.partial({ tab: tab.id, }); }; onFieldConfigChange = (config: FieldConfigSource) => { // we do not need to trigger force update here as the function call below // fires PanelOptionsChangedEvent which we subscribe to above this.props.panel.updateFieldConfig({ ...config, }); }; onPanelOptionsChanged = (options: PanelModel['options']) => { // we do not need to trigger force update here as the function call below // fires PanelOptionsChangedEvent which we subscribe to above this.props.panel.updateOptions(options); }; onPanelConfigChanged = (configKey: keyof PanelModel, value: unknown) => { this.props.panel.setProperty(configKey, value); this.props.panel.render(); this.forceUpdate(); }; onDisplayModeChange = (mode?: DisplayMode) => { const { updatePanelEditorUIState } = this.props; if (this.props.tableViewEnabled) { this.props.toggleTableView(); } updatePanelEditorUIState({ mode: mode, }); }; onToggleTableView = () => { this.props.toggleTableView(); }; renderPanel(styles: EditorStyles, isOnlyPanel: boolean) { const { dashboard, panel, uiState, tableViewEnabled, theme } = this.props; return ( <div className={styles.mainPaneWrapper} key="panel"> {this.renderPanelToolbar(styles)} <div className={styles.panelWrapper}> <AutoSizer> {({ width, height }) => { if (width < 3 || height < 3) { return null; } // If no tabs limit height so panel does not extend to edge if (isOnlyPanel) { height -= theme.spacing.gridSize * 2; } if (tableViewEnabled) { return <PanelEditorTableView width={width} height={height} panel={panel} dashboard={dashboard} />; } const panelSize = calculatePanelSize(uiState.mode, width, height, panel); return ( <div className={styles.centeringContainer} style={{ width, height }}> <div style={panelSize} data-panelid={panel.id}> <DashboardPanel key={panel.key} stateKey={panel.key} dashboard={dashboard} panel={panel} isEditing={true} isViewing={false} lazy={false} width={panelSize.width} height={panelSize.height} /> </div> </div> ); }} </AutoSizer> </div> </div> ); } renderPanelAndEditor(uiState: PanelEditorUIState, styles: EditorStyles) { const { panel, dashboard, plugin, tab } = this.props; const tabs = getPanelEditorTabs(tab, plugin); const isOnlyPanel = tabs.length === 0; const panelPane = this.renderPanel(styles, isOnlyPanel); if (tabs.length === 0) { return <div className={styles.onlyPanel}>{panelPane}</div>; } return ( <SplitPaneWrapper splitOrientation="horizontal" maxSize={-200} paneSize={uiState.topPaneSize} primary="first" secondaryPaneStyle={{ minHeight: 0 }} onDragFinished={(size) => { if (size) { updatePanelEditorUIState({ topPaneSize: size / window.innerHeight }); } }} > {panelPane} <div className={styles.tabsWrapper} aria-label={selectors.components.PanelEditor.DataPane.content} key="panel-editor-tabs" > <PanelEditorTabs key={panel.key} panel={panel} dashboard={dashboard} tabs={tabs} onChangeTab={this.onChangeTab} /> </div> </SplitPaneWrapper> ); } renderTemplateVariables(styles: EditorStyles) { const { variables } = this.props; if (!variables.length) { return null; } return ( <div className={styles.variablesWrapper}> <SubMenuItems variables={variables} /> </div> ); } renderPanelToolbar(styles: EditorStyles) { const { dashboard, uiState, variables, updateTimeZoneForSession, panel, tableViewEnabled } = this.props; return ( <div className={styles.panelToolbar}> <HorizontalGroup justify={variables.length > 0 ? 'space-between' : 'flex-end'} align="flex-start"> {this.renderTemplateVariables(styles)} <Stack gap={1}> <InlineSwitch label="Table view" showLabel={true} id="table-view" value={tableViewEnabled} onClick={this.onToggleTableView} aria-label={selectors.components.PanelEditor.toggleTableView} /> <RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDisplayModeChange} /> <DashNavTimeControls dashboard={dashboard} onChangeTimeZone={updateTimeZoneForSession} isOnCanvas={true} /> {!uiState.isPanelOptionsVisible && <VisualizationButton panel={panel} />} </Stack> </HorizontalGroup> </div> ); } renderEditorActions() { const size = 'sm'; let editorActions = [ <Button onClick={this.onDiscard} title="Undo all changes" key="discard" size={size} variant="destructive" fill="outline" > Discard </Button>, this.props.panel.libraryPanel ? ( <Button onClick={this.onSaveLibraryPanel} variant="primary" size={size} title="Apply changes and save library panel" key="save-panel" > Save library panel </Button> ) : ( <Button onClick={this.onSaveDashboard} title="Apply changes and save dashboard" key="save" size={size} variant="secondary" > Save </Button> ), <Button onClick={this.onBack} variant="primary" title="Apply changes and go back to dashboard" data-testid={selectors.components.PanelEditor.applyButton} key="apply" size={size} > Apply </Button>, ]; if (this.props.panel.libraryPanel) { editorActions.splice( 1, 0, <ModalsController key="unlink-controller"> {({ showModal, hideModal }) => { return ( <ToolbarButton onClick={() => { showModal(UnlinkModal, { onConfirm: () => { this.props.panel.unlinkLibraryPanel(); this.forceUpdate(); }, onDismiss: hideModal, isOpen: true, }); }} title="Disconnects this panel from the library panel so that you can edit it regularly." key="unlink" > Unlink </ToolbarButton> ); }} </ModalsController> ); // Remove "Apply" button editorActions.pop(); } return editorActions; } renderOptionsPane() { const { plugin, dashboard, panel, instanceState } = this.props; if (!plugin) { return <div />; } return ( <OptionsPane plugin={plugin} dashboard={dashboard} panel={panel} instanceState={instanceState} onFieldConfigsChange={this.onFieldConfigChange} onPanelOptionsChanged={this.onPanelOptionsChanged} onPanelConfigChange={this.onPanelConfigChanged} /> ); } onGoBackToDashboard = () => { locationService.partial({ editPanel: null, tab: null, showCategory: null }); }; onConfirmAndDismissLibarayPanelModel = () => { this.setState({ showSaveLibraryPanelModal: false }); }; render() { const { initDone, uiState, theme, sectionNav, pageNav, className, updatePanelEditorUIState } = this.props; const styles = getStyles(theme, this.props); if (!initDone) { return null; } return ( <Page navModel={sectionNav} pageNav={pageNav} aria-label={selectors.components.PanelEditor.General.content} layout={PageLayoutType.Custom} className={className} > <AppChromeUpdate actions={<ToolbarButtonRow alignment="right">{this.renderEditorActions()}</ToolbarButtonRow>} /> <div className={styles.wrapper}> <div className={styles.verticalSplitPanesWrapper}> {!uiState.isPanelOptionsVisible ? ( this.renderPanelAndEditor(uiState, styles) ) : ( <SplitPaneWrapper splitOrientation="vertical" maxSize={-300} paneSize={uiState.rightPaneSize} primary="second" onDragFinished={(size) => { if (size) { updatePanelEditorUIState({ rightPaneSize: size / window.innerWidth }); } }} > {this.renderPanelAndEditor(uiState, styles)} {this.renderOptionsPane()} </SplitPaneWrapper> )} </div> {this.state.showSaveLibraryPanelModal && ( <SaveLibraryPanelModal panel={this.props.panel as PanelModelWithLibraryPanel} folderUid={this.props.dashboard.meta.folderUid ?? ''} onConfirm={this.onConfirmAndDismissLibarayPanelModel} onDiscard={this.onDiscard} onDismiss={this.onConfirmAndDismissLibarayPanelModel} /> )} </div> </Page> ); } } export const PanelEditor = withTheme2(connector(PanelEditorUnconnected)); /* * Styles */ export const getStyles = stylesFactory((theme: GrafanaTheme2, props: Props) => { const { uiState } = props; const paneSpacing = theme.spacing(2); return { wrapper: css({ width: '100%', flexGrow: 1, minHeight: 0, display: 'flex', paddingTop: theme.spacing(2), }), verticalSplitPanesWrapper: css` display: flex; flex-direction: column; height: 100%; width: 100%; position: relative; `, mainPaneWrapper: css` display: flex; flex-direction: column; height: 100%; width: 100%; padding-right: ${uiState.isPanelOptionsVisible ? 0 : paneSpacing}; `, variablesWrapper: css` label: variablesWrapper; display: flex; flex-grow: 1; flex-wrap: wrap; gap: ${theme.spacing(1, 2)}; `, panelWrapper: css` flex: 1 1 0; min-height: 0; width: 100%; padding-left: ${paneSpacing}; `, tabsWrapper: css` height: 100%; width: 100%; `, panelToolbar: css` display: flex; padding: 0 0 ${paneSpacing} ${paneSpacing}; justify-content: space-between; flex-wrap: wrap; `, angularWarning: css` display: flex; height: theme.spacing(4); align-items: center; `, toolbarLeft: css` padding-left: ${theme.spacing(1)}; `, centeringContainer: css` display: flex; justify-content: center; align-items: center; position: relative; flex-direction: column; `, onlyPanel: css` height: 100%; position: absolute; overflow: hidden; width: 100%; `, }; }); type EditorStyles = ReturnType<typeof getStyles>;
Edit
Rename
Chmod
Delete
FILE
FOLDER
INFO
Name
Size
Permission
Action
state
---
0755
AngularPanelOptions.tsx
3907 bytes
0644
DynamicConfigValueEditor.tsx
3678 bytes
0644
OptionsPane.tsx
2615 bytes
0644
OptionsPaneCategory.tsx
5759 bytes
0644
OptionsPaneCategoryDescriptor.tsx
1835 bytes
0644
OptionsPaneItemDescriptor.tsx
3584 bytes
0644
OptionsPaneItemOverrides.tsx
1208 bytes
0644
OptionsPaneOptions.test.tsx
9120 bytes
0644
OptionsPaneOptions.tsx
7812 bytes
0644
OverrideCategoryTitle.tsx
2069 bytes
0644
PanelEditor.tsx
16794 bytes
0644
PanelEditorQueries.tsx
4139 bytes
0644
PanelEditorTableView.test.tsx
7264 bytes
0644
PanelEditorTableView.tsx
2203 bytes
0644
PanelEditorTabs.tsx
4931 bytes
0644
PanelHeaderCorner.test.tsx
708 bytes
0644
PanelHeaderCorner.tsx
3489 bytes
0644
PanelNotSupported.test.tsx
1398 bytes
0644
PanelNotSupported.tsx
849 bytes
0644
VisualizationButton.tsx
2277 bytes
0644
VisualizationSelectPane.tsx
6512 bytes
0644
getFieldOverrideElements.tsx
8273 bytes
0644
getLibraryPanelOptions.tsx
1704 bytes
0644
getPanelFrameOptions.tsx
11327 bytes
0644
getVisualizationOptions.test.ts
4366 bytes
0644
getVisualizationOptions.tsx
9787 bytes
0644
types.ts
1823 bytes
0644
usePanelLatestData.ts
2206 bytes
0644
utils.test.ts
5250 bytes
0644
utils.ts
2895 bytes
0644
N4ST4R_ID | Naxtarrr