View File Name : DashboardPageProxy.9a6d60e5cac89c11c71c.js.map
\n }\n confirmText=\"Delete alert\"\n onDismiss={onDismiss}\n onConfirm={() => {\n delete panel.alert;\n panel.thresholds = [];\n if (this.panelCtrl) {\n this.panelCtrl.alertState = null;\n this.panelCtrl.render();\n }\n this.component?.digest();\n onDismiss();\n }}\n />\n );\n };\n\n renderStateHistory = () => {\n if (!this.state.showStateHistory) {\n return null;\n }\n\n const { panel, dashboard } = this.props;\n const onDismiss = () => this.onToggleModal('showStateHistory');\n\n return (\n
\n this.panelCtrl?.refresh()} />\n \n );\n };\n\n render() {\n const { alert, transformations } = this.props.panel;\n const { validationMessage } = this.state;\n const hasTransformations = transformations && transformations.length > 0;\n\n if (!alert && validationMessage) {\n return
;\n }\n\n const model = {\n title: 'Panel has no alert rule defined',\n buttonIcon: 'bell' as const,\n onClick: this.onAddAlert,\n buttonTitle: 'Create Alert',\n };\n\n return (\n <>\n
\n \n \n {alert && hasTransformations && (\n
\n )}\n\n
(this.element = element)} />\n {alert && (\n \n this.onToggleModal('showStateHistory')} variant=\"secondary\">\n State history\n \n this.onToggleModal('showTestRule')} variant=\"secondary\">\n Test rule\n \n this.onToggleModal('showDeleteConfirmation')} variant=\"destructive\">\n Delete\n \n \n )}\n {!alert && !validationMessage && }\n
\n \n \n\n {this.renderTestRule()}\n {this.renderDeleteConfirmation()}\n {this.renderStateHistory()}\n >\n );\n }\n}\n\nconst mapStateToProps: MapStateToProps
= (state, props) => {\n return {\n angularPanelComponent: getPanelStateForModel(state, props.panel)?.angularComponent,\n };\n};\n\nconst mapDispatchToProps: MapDispatchToProps = {};\n\nexport const AlertTab = connect(mapStateToProps, mapDispatchToProps)(UnConnectedAlertTab);\n","import React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useAsync } from 'react-use';\n\nimport { urlUtil } from '@grafana/data';\nimport { Alert, Button, LinkButton } from '@grafana/ui';\nimport { DashboardModel, PanelModel } from 'app/features/dashboard/state';\nimport { useSelector } from 'app/types';\n\nimport { logInfo, LogMessages } from '../../Analytics';\nimport { panelToRuleFormValues } from '../../utils/rule-form';\n\ninterface Props {\n panel: PanelModel;\n dashboard: DashboardModel;\n className?: string;\n}\n\nexport const NewRuleFromPanelButton = ({ dashboard, panel, className }: Props) => {\n const templating = useSelector((state) => {\n return state.templating;\n });\n\n const location = useLocation();\n\n const { loading, value: formValues } = useAsync(\n () => panelToRuleFormValues(panel, dashboard),\n // Templating variables are required to update formValues on each variable's change. It's used implicitly by the templating engine\n [panel, dashboard, templating]\n );\n\n if (loading) {\n return New alert rule ;\n }\n\n if (!formValues) {\n return (\n \n Cannot create alerts from this panel because no query to an alerting capable datasource is found.\n \n );\n }\n\n const ruleFormUrl = urlUtil.renderUrl('alerting/new', {\n defaults: JSON.stringify(formValues),\n returnTo: location.pathname + location.search,\n });\n\n return (\n logInfo(LogMessages.alertRuleFromPanel)}\n href={ruleFormUrl}\n className={className}\n data-testid=\"create-alert-rule-button\"\n >\n New alert rule\n \n );\n};\n","import { css } from '@emotion/css';\nimport React from 'react';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { Alert, CustomScrollbar, LoadingPlaceholder, useStyles2 } from '@grafana/ui';\nimport { contextSrv } from 'app/core/services/context_srv';\nimport { DashboardModel, PanelModel } from 'app/features/dashboard/state';\n\nimport { NewRuleFromPanelButton } from './components/panel-alerts-tab/NewRuleFromPanelButton';\nimport { RulesTable } from './components/rules/RulesTable';\nimport { usePanelCombinedRules } from './hooks/usePanelCombinedRules';\nimport { getRulesPermissions } from './utils/access-control';\n\ninterface Props {\n dashboard: DashboardModel;\n panel: PanelModel;\n}\n\nexport const PanelAlertTabContent = ({ dashboard, panel }: Props) => {\n const styles = useStyles2(getStyles);\n const { errors, loading, rules } = usePanelCombinedRules({\n dashboardUID: dashboard.uid,\n panelId: panel.id,\n poll: true,\n });\n const permissions = getRulesPermissions('grafana');\n const canCreateRules = contextSrv.hasPermission(permissions.create);\n\n const alert = errors.length ? (\n \n {errors.map((error, index) => (\n Failed to load Grafana rules state: {error.message || 'Unknown error.'}
\n ))}\n \n ) : null;\n\n if (loading && !rules.length) {\n return (\n \n {alert}\n \n
\n );\n }\n\n if (rules.length) {\n return (\n \n \n {alert}\n \n {!!dashboard.meta.canSave && canCreateRules && (\n \n )}\n
\n \n );\n }\n\n return (\n \n {alert}\n {!!dashboard.uid && (\n <>\n
There are no alert rules linked to this panel.
\n {!!dashboard.meta.canSave && canCreateRules &&
}\n >\n )}\n {!dashboard.uid && !!dashboard.meta.canSave && (\n
\n Dashboard must be saved before alerts can be added.\n \n )}\n
\n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n newButton: css`\n margin-top: ${theme.spacing(3)};\n `,\n innerWrapper: css`\n padding: ${theme.spacing(2)};\n `,\n noRulesWrapper: css`\n margin: ${theme.spacing(2)};\n background-color: ${theme.colors.background.secondary};\n padding: ${theme.spacing(3)};\n `,\n});\n","import { config } from '@grafana/runtime';\n\nimport { AlertTab } from './AlertTab';\nimport { PanelAlertTabContent } from './unified/PanelAlertTabContent';\n\n// route between unified and \"old\" alerting pages based on feature flag\n\nexport default config.unifiedAlertingEnabled ? PanelAlertTabContent : AlertTab;\n","import React from 'react';\n\nimport { Tab, TabProps } from '@grafana/ui';\nimport { DashboardModel, PanelModel } from 'app/features/dashboard/state';\n\nimport { usePanelCombinedRules } from './hooks/usePanelCombinedRules';\n\ninterface Props extends Omit {\n panel: PanelModel;\n dashboard: DashboardModel;\n}\n\n// it will load rule count from backend\nexport const PanelAlertTab = ({ panel, dashboard, ...otherProps }: Props) => {\n const { rules, loading } = usePanelCombinedRules({ panelId: panel.id, dashboardUID: dashboard.uid });\n return ;\n};\n","import React, { PureComponent } from 'react';\n\nimport { DataQuery, getDataSourceRef } from '@grafana/data';\nimport { locationService } from '@grafana/runtime';\nimport { storeLastUsedDataSourceInLocalStorage } from 'app/features/datasources/components/picker/utils';\nimport { getDatasourceSrv } from 'app/features/plugins/datasource_srv';\nimport { QueryGroup } from 'app/features/query/components/QueryGroup';\nimport { QueryGroupDataSource, QueryGroupOptions } from 'app/types';\n\nimport { getDashboardSrv } from '../../services/DashboardSrv';\nimport { PanelModel } from '../../state';\nimport { getLastUsedDatasourceFromStorage } from '../../utils/dashboard';\n\ninterface Props {\n /** Current panel */\n panel: PanelModel;\n /** Added here to make component re-render when queries change from outside */\n queries: DataQuery[];\n}\n\nexport class PanelEditorQueries extends PureComponent {\n constructor(props: Props) {\n super(props);\n }\n\n // store last used datasource in local storage\n updateLastUsedDatasource = (datasource: QueryGroupDataSource) => {\n storeLastUsedDataSourceInLocalStorage(datasource);\n };\n\n buildQueryOptions(panel: PanelModel): QueryGroupOptions {\n const dataSource: QueryGroupDataSource = panel.datasource ?? {\n default: true,\n };\n const datasourceSettings = getDatasourceSrv().getInstanceSettings(dataSource);\n\n // store last datasource used in local storage\n this.updateLastUsedDatasource(dataSource);\n return {\n cacheTimeout: datasourceSettings?.meta.queryOptions?.cacheTimeout ? panel.cacheTimeout : undefined,\n dataSource: {\n default: datasourceSettings?.isDefault,\n type: datasourceSettings?.type,\n uid: datasourceSettings?.uid,\n },\n queryCachingTTL: datasourceSettings?.cachingConfig?.enabled ? panel.queryCachingTTL : undefined,\n queries: panel.targets,\n maxDataPoints: panel.maxDataPoints,\n minInterval: panel.interval,\n timeRange: {\n from: panel.timeFrom,\n shift: panel.timeShift,\n hide: panel.hideTimeOverride,\n },\n };\n }\n\n async componentDidMount() {\n const { panel } = this.props;\n\n // If the panel model has no datasource property load the default data source property and update the persisted model\n // Because this part of the panel model is not in redux yet we do a forceUpdate.\n if (!panel.datasource) {\n let ds;\n // check if we have last used datasource from local storage\n // get dashboard uid\n const dashboardUid = getDashboardSrv().getCurrent()?.uid ?? '';\n const lastUsedDatasource = getLastUsedDatasourceFromStorage(dashboardUid!);\n // do we have a last used datasource for this dashboard\n if (lastUsedDatasource?.datasourceUid !== null) {\n // get datasource from uid\n ds = getDatasourceSrv().getInstanceSettings(lastUsedDatasource?.datasourceUid);\n }\n // else load default datasource\n if (!ds) {\n ds = getDatasourceSrv().getInstanceSettings(null);\n }\n panel.datasource = getDataSourceRef(ds!);\n this.forceUpdate();\n }\n }\n\n onRunQueries = () => {\n this.props.panel.refresh();\n };\n\n onOpenQueryInspector = () => {\n locationService.partial({\n inspect: this.props.panel.id,\n inspectTab: 'query',\n });\n };\n\n onOptionsChange = (options: QueryGroupOptions) => {\n const { panel } = this.props;\n\n panel.updateQueries(options);\n\n if (options.dataSource.uid !== panel.datasource?.uid) {\n // trigger queries when changing data source\n setTimeout(this.onRunQueries, 10);\n }\n\n this.forceUpdate();\n };\n\n render() {\n const { panel } = this.props;\n\n // If no panel data soruce set, wait with render. Will be set to default in componentDidMount\n if (!panel.datasource) {\n return null;\n }\n\n const options = this.buildQueryOptions(panel);\n\n return (\n \n );\n }\n}\n","import { css } from '@emotion/css';\nimport React, { useEffect, useCallback } from 'react';\nimport { Subscription } from 'rxjs';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { config, reportInteraction } from '@grafana/runtime';\nimport { Tab, TabContent, TabsBar, toIconName, useForceUpdate, useStyles2 } from '@grafana/ui';\nimport AlertTabIndex from 'app/features/alerting/AlertTabIndex';\nimport { PanelAlertTab } from 'app/features/alerting/unified/PanelAlertTab';\nimport { PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events';\n\nimport { DashboardModel, PanelModel } from '../../state';\nimport { TransformationsEditor } from '../TransformationsEditor/TransformationsEditor';\n\nimport { PanelEditorQueries } from './PanelEditorQueries';\nimport { PanelEditorTab, PanelEditorTabId } from './types';\n\ninterface PanelEditorTabsProps {\n panel: PanelModel;\n dashboard: DashboardModel;\n tabs: PanelEditorTab[];\n onChangeTab: (tab: PanelEditorTab) => void;\n}\n\nexport const PanelEditorTabs = React.memo(({ panel, dashboard, tabs, onChangeTab }: PanelEditorTabsProps) => {\n const forceUpdate = useForceUpdate();\n const styles = useStyles2(getStyles);\n\n const instrumentedOnChangeTab = useCallback(\n (tab: PanelEditorTab) => {\n let eventName = 'panel_editor_tabs_changed';\n if (config.featureToggles.transformationsRedesign) {\n eventName = 'transformations_redesign_' + eventName;\n }\n\n if (!tab.active) {\n reportInteraction(eventName, { tab_id: tab.id });\n }\n\n onChangeTab(tab);\n },\n [onChangeTab]\n );\n\n useEffect(() => {\n const eventSubs = new Subscription();\n eventSubs.add(panel.events.subscribe(PanelQueriesChangedEvent, forceUpdate));\n eventSubs.add(panel.events.subscribe(PanelTransformationsChangedEvent, forceUpdate));\n return () => eventSubs.unsubscribe();\n }, [panel, dashboard, forceUpdate]);\n\n const activeTab = tabs.find((item) => item.active)!;\n\n if (tabs.length === 0) {\n return null;\n }\n\n return (\n \n
\n {tabs.map((tab) => {\n if (tab.id === PanelEditorTabId.Alert) {\n return renderAlertTab(tab, panel, dashboard, instrumentedOnChangeTab);\n }\n return (\n instrumentedOnChangeTab(tab)}\n icon={toIconName(tab.icon)}\n counter={getCounter(panel, tab)}\n />\n );\n })}\n \n
\n {activeTab.id === PanelEditorTabId.Query && }\n {activeTab.id === PanelEditorTabId.Alert && }\n {activeTab.id === PanelEditorTabId.Transform && }\n \n
\n );\n});\n\nPanelEditorTabs.displayName = 'PanelEditorTabs';\n\nfunction getCounter(panel: PanelModel, tab: PanelEditorTab) {\n switch (tab.id) {\n case PanelEditorTabId.Query:\n return panel.targets.length;\n case PanelEditorTabId.Alert:\n return panel.alert ? 1 : 0;\n case PanelEditorTabId.Transform:\n const transformations = panel.getTransformations() ?? [];\n return transformations.length;\n }\n\n return null;\n}\n\nfunction renderAlertTab(\n tab: PanelEditorTab,\n panel: PanelModel,\n dashboard: DashboardModel,\n onChangeTab: (tab: PanelEditorTab) => void\n) {\n const alertingDisabled = !config.alertingEnabled && !config.unifiedAlertingEnabled;\n\n if (alertingDisabled) {\n return null;\n }\n\n if (config.unifiedAlertingEnabled) {\n return (\n onChangeTab(tab)}\n icon={toIconName(tab.icon)}\n panel={panel}\n dashboard={dashboard}\n />\n );\n }\n\n if (config.alertingEnabled) {\n return (\n onChangeTab(tab)}\n icon={toIconName(tab.icon)}\n counter={getCounter(panel, tab)}\n />\n );\n }\n\n return null;\n}\n\nconst getStyles = (theme: GrafanaTheme2) => {\n return {\n wrapper: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n `,\n tabBar: css`\n padding-left: ${theme.spacing(2)};\n `,\n tabContent: css`\n padding: 0;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: ${theme.colors.background.primary};\n border: 1px solid ${theme.components.panel.borderColor};\n border-left: none;\n border-bottom: none;\n border-top-right-radius: ${theme.shape.borderRadius(1.5)};\n `,\n };\n};\n","import { css } from '@emotion/css';\nimport React, { PureComponent } from 'react';\nimport { connect, ConnectedProps } from 'react-redux';\nimport AutoSizer from 'react-virtualized-auto-sizer';\nimport { Subscription } from 'rxjs';\n\nimport { FieldConfigSource, GrafanaTheme2, NavModel, NavModelItem, PageLayoutType } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { locationService } from '@grafana/runtime';\nimport {\n Button,\n HorizontalGroup,\n InlineSwitch,\n ModalsController,\n RadioButtonGroup,\n stylesFactory,\n Themeable2,\n ToolbarButton,\n ToolbarButtonRow,\n withTheme2,\n Stack,\n} from '@grafana/ui';\nimport { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';\nimport { Page } from 'app/core/components/Page/Page';\nimport { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper';\nimport { appEvents } from 'app/core/core';\nimport { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';\nimport { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal';\nimport { PanelModelWithLibraryPanel } from 'app/features/library-panels/types';\nimport { getPanelStateForModel } from 'app/features/panel/state/selectors';\nimport { updateTimeZoneForSession } from 'app/features/profile/state/reducers';\nimport { StoreState } from 'app/types';\nimport { PanelOptionsChangedEvent, ShowModalReactEvent } from 'app/types/events';\n\nimport { notifyApp } from '../../../../core/actions';\nimport { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal';\nimport { isPanelModelLibraryPanel } from '../../../library-panels/guard';\nimport { getVariablesByKey } from '../../../variables/state/selectors';\nimport { DashboardPanel } from '../../dashgrid/DashboardPanel';\nimport { DashboardModel, PanelModel } from '../../state';\nimport { DashNavTimeControls } from '../DashNav/DashNavTimeControls';\nimport { SaveDashboardDrawer } from '../SaveDashboard/SaveDashboardDrawer';\n\nimport { OptionsPane } from './OptionsPane';\nimport { PanelEditorTableView } from './PanelEditorTableView';\nimport { PanelEditorTabs } from './PanelEditorTabs';\nimport { VisualizationButton } from './VisualizationButton';\nimport { discardPanelChanges, initPanelEditor, updatePanelEditorUIState } from './state/actions';\nimport { PanelEditorUIState, toggleTableView } from './state/reducers';\nimport { getPanelEditorTabs } from './state/selectors';\nimport { DisplayMode, displayModes, PanelEditorTab } from './types';\nimport { calculatePanelSize } from './utils';\n\ninterface OwnProps {\n dashboard: DashboardModel;\n sourcePanel: PanelModel;\n sectionNav: NavModel;\n pageNav: NavModelItem;\n className?: string;\n tab?: string;\n}\n\nconst mapStateToProps = (state: StoreState, ownProps: OwnProps) => {\n const panel = state.panelEditor.getPanel();\n const panelState = getPanelStateForModel(state, panel);\n\n return {\n panel,\n plugin: panelState?.plugin,\n instanceState: panelState?.instanceState,\n initDone: state.panelEditor.initDone,\n uiState: state.panelEditor.ui,\n tableViewEnabled: state.panelEditor.tableViewEnabled,\n variables: getVariablesByKey(ownProps.dashboard.uid, state),\n };\n};\n\nconst mapDispatchToProps = {\n initPanelEditor,\n discardPanelChanges,\n updatePanelEditorUIState,\n updateTimeZoneForSession,\n toggleTableView,\n notifyApp,\n};\n\nconst connector = connect(mapStateToProps, mapDispatchToProps);\n\ntype Props = OwnProps & ConnectedProps & Themeable2;\n\ninterface State {\n showSaveLibraryPanelModal?: boolean;\n}\n\nexport class PanelEditorUnconnected extends PureComponent {\n private eventSubs?: Subscription;\n\n state: State = {\n showSaveLibraryPanelModal: false,\n };\n\n componentDidMount() {\n this.props.initPanelEditor(this.props.sourcePanel, this.props.dashboard);\n }\n\n componentDidUpdate() {\n const { panel, initDone } = this.props;\n\n if (initDone && !this.eventSubs) {\n this.eventSubs = new Subscription();\n this.eventSubs.add(panel.events.subscribe(PanelOptionsChangedEvent, this.triggerForceUpdate));\n }\n }\n\n componentWillUnmount() {\n // redux action exitPanelEditor is called on location change from DashboardPrompt\n this.eventSubs?.unsubscribe();\n }\n\n triggerForceUpdate = () => {\n this.forceUpdate();\n };\n\n onBack = () => {\n locationService.partial({\n editPanel: null,\n tab: null,\n showCategory: null,\n });\n };\n\n onDiscard = () => {\n this.props.discardPanelChanges();\n this.onBack();\n };\n\n onSaveDashboard = () => {\n appEvents.publish(\n new ShowModalReactEvent({\n component: SaveDashboardDrawer,\n props: { dashboard: this.props.dashboard },\n })\n );\n };\n\n onSaveLibraryPanel = async () => {\n if (!isPanelModelLibraryPanel(this.props.panel)) {\n // New library panel, no need to display modal\n return;\n }\n\n this.setState({ showSaveLibraryPanelModal: true });\n };\n\n onChangeTab = (tab: PanelEditorTab) => {\n locationService.partial({\n tab: tab.id,\n });\n };\n\n onFieldConfigChange = (config: FieldConfigSource) => {\n // we do not need to trigger force update here as the function call below\n // fires PanelOptionsChangedEvent which we subscribe to above\n this.props.panel.updateFieldConfig({\n ...config,\n });\n };\n\n onPanelOptionsChanged = (options: PanelModel['options']) => {\n // we do not need to trigger force update here as the function call below\n // fires PanelOptionsChangedEvent which we subscribe to above\n this.props.panel.updateOptions(options);\n };\n\n onPanelConfigChanged = (configKey: keyof PanelModel, value: unknown) => {\n this.props.panel.setProperty(configKey, value);\n this.props.panel.render();\n this.forceUpdate();\n };\n\n onDisplayModeChange = (mode?: DisplayMode) => {\n const { updatePanelEditorUIState } = this.props;\n if (this.props.tableViewEnabled) {\n this.props.toggleTableView();\n }\n updatePanelEditorUIState({\n mode: mode,\n });\n };\n\n onToggleTableView = () => {\n this.props.toggleTableView();\n };\n\n renderPanel(styles: EditorStyles, isOnlyPanel: boolean) {\n const { dashboard, panel, uiState, tableViewEnabled, theme } = this.props;\n\n return (\n \n {this.renderPanelToolbar(styles)}\n
\n
\n {({ width, height }) => {\n if (width < 3 || height < 3) {\n return null;\n }\n\n // If no tabs limit height so panel does not extend to edge\n if (isOnlyPanel) {\n height -= theme.spacing.gridSize * 2;\n }\n\n if (tableViewEnabled) {\n return ;\n }\n\n const panelSize = calculatePanelSize(uiState.mode, width, height, panel);\n\n return (\n \n );\n }}\n \n
\n
\n );\n }\n\n renderPanelAndEditor(uiState: PanelEditorUIState, styles: EditorStyles) {\n const { panel, dashboard, plugin, tab } = this.props;\n const tabs = getPanelEditorTabs(tab, plugin);\n const isOnlyPanel = tabs.length === 0;\n const panelPane = this.renderPanel(styles, isOnlyPanel);\n\n if (tabs.length === 0) {\n return {panelPane}
;\n }\n\n return (\n {\n if (size) {\n updatePanelEditorUIState({ topPaneSize: size / window.innerHeight });\n }\n }}\n >\n {panelPane}\n \n \n );\n }\n\n renderTemplateVariables(styles: EditorStyles) {\n const { variables } = this.props;\n\n if (!variables.length) {\n return null;\n }\n\n return (\n \n \n
\n );\n }\n\n renderPanelToolbar(styles: EditorStyles) {\n const { dashboard, uiState, variables, updateTimeZoneForSession, panel, tableViewEnabled } = this.props;\n\n return (\n \n 0 ? 'space-between' : 'flex-end'} align=\"flex-start\">\n {this.renderTemplateVariables(styles)}\n \n \n \n \n {!uiState.isPanelOptionsVisible && }\n \n \n
\n );\n }\n\n renderEditorActions() {\n const size = 'sm';\n let editorActions = [\n \n Discard\n ,\n this.props.panel.libraryPanel ? (\n \n Save library panel\n \n ) : (\n \n Save\n \n ),\n \n Apply\n ,\n ];\n\n if (this.props.panel.libraryPanel) {\n editorActions.splice(\n 1,\n 0,\n \n {({ showModal, hideModal }) => {\n return (\n {\n showModal(UnlinkModal, {\n onConfirm: () => {\n this.props.panel.unlinkLibraryPanel();\n this.forceUpdate();\n },\n onDismiss: hideModal,\n isOpen: true,\n });\n }}\n title=\"Disconnects this panel from the library panel so that you can edit it regularly.\"\n key=\"unlink\"\n >\n Unlink\n \n );\n }}\n \n );\n\n // Remove \"Apply\" button\n editorActions.pop();\n }\n\n return editorActions;\n }\n\n renderOptionsPane() {\n const { plugin, dashboard, panel, instanceState } = this.props;\n\n if (!plugin) {\n return
;\n }\n\n return (\n \n );\n }\n\n onGoBackToDashboard = () => {\n locationService.partial({ editPanel: null, tab: null, showCategory: null });\n };\n\n onConfirmAndDismissLibarayPanelModel = () => {\n this.setState({ showSaveLibraryPanelModal: false });\n };\n\n render() {\n const { initDone, uiState, theme, sectionNav, pageNav, className, updatePanelEditorUIState } = this.props;\n const styles = getStyles(theme, this.props);\n\n if (!initDone) {\n return null;\n }\n\n return (\n \n {this.renderEditorActions()}}\n />\n \n
\n {!uiState.isPanelOptionsVisible ? (\n this.renderPanelAndEditor(uiState, styles)\n ) : (\n {\n if (size) {\n updatePanelEditorUIState({ rightPaneSize: size / window.innerWidth });\n }\n }}\n >\n {this.renderPanelAndEditor(uiState, styles)}\n {this.renderOptionsPane()}\n \n )}\n
\n {this.state.showSaveLibraryPanelModal && (\n
\n )}\n
\n \n );\n }\n}\n\nexport const PanelEditor = withTheme2(connector(PanelEditorUnconnected));\n\n/*\n * Styles\n */\nexport const getStyles = stylesFactory((theme: GrafanaTheme2, props: Props) => {\n const { uiState } = props;\n const paneSpacing = theme.spacing(2);\n\n return {\n wrapper: css({\n width: '100%',\n flexGrow: 1,\n minHeight: 0,\n display: 'flex',\n paddingTop: theme.spacing(2),\n }),\n verticalSplitPanesWrapper: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n position: relative;\n `,\n mainPaneWrapper: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n padding-right: ${uiState.isPanelOptionsVisible ? 0 : paneSpacing};\n `,\n variablesWrapper: css`\n label: variablesWrapper;\n display: flex;\n flex-grow: 1;\n flex-wrap: wrap;\n gap: ${theme.spacing(1, 2)};\n `,\n panelWrapper: css`\n flex: 1 1 0;\n min-height: 0;\n width: 100%;\n padding-left: ${paneSpacing};\n `,\n tabsWrapper: css`\n height: 100%;\n width: 100%;\n `,\n panelToolbar: css`\n display: flex;\n padding: 0 0 ${paneSpacing} ${paneSpacing};\n justify-content: space-between;\n flex-wrap: wrap;\n `,\n angularWarning: css`\n display: flex;\n height: theme.spacing(4);\n align-items: center;\n `,\n toolbarLeft: css`\n padding-left: ${theme.spacing(1)};\n `,\n centeringContainer: css`\n display: flex;\n justify-content: center;\n align-items: center;\n position: relative;\n flex-direction: column;\n `,\n onlyPanel: css`\n height: 100%;\n position: absolute;\n overflow: hidden;\n width: 100%;\n `,\n };\n});\n\ntype EditorStyles = ReturnType;\n","import { css } from '@emotion/css';\nimport React, { useEffect, useState } from 'react';\n\nimport { AnnotationQuery, EventBus, GrafanaTheme2 } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { InlineField, InlineFieldRow, InlineSwitch, useStyles2 } from '@grafana/ui';\nimport { LoadingIndicator } from '@grafana/ui/src/components/PanelChrome/LoadingIndicator';\n\nimport { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events';\nimport { getDashboardQueryRunner } from '../../../query/state/DashboardQueryRunner/DashboardQueryRunner';\n\nexport interface AnnotationPickerProps {\n events: EventBus;\n annotation: AnnotationQuery;\n onEnabledChanged: (annotation: AnnotationQuery) => void;\n}\n\nexport const AnnotationPicker = ({ annotation, events, onEnabledChanged }: AnnotationPickerProps): JSX.Element => {\n const [loading, setLoading] = useState(false);\n const styles = useStyles2(getStyles);\n const onCancel = () => getDashboardQueryRunner().cancel(annotation);\n\n useEffect(() => {\n const started = events.getStream(AnnotationQueryStarted).subscribe({\n next: (event) => {\n if (event.payload === annotation) {\n setLoading(true);\n }\n },\n });\n const stopped = events.getStream(AnnotationQueryFinished).subscribe({\n next: (event) => {\n if (event.payload === annotation) {\n setLoading(false);\n }\n },\n });\n\n return () => {\n started.unsubscribe();\n stopped.unsubscribe();\n };\n });\n\n return (\n \n
\n \n onEnabledChanged(annotation)}\n disabled={loading}\n data-testid={selectors.pages.Dashboard.SubMenu.Annotations.annotationToggle(annotation.name)}\n />\n \n \n \n
\n \n
\n );\n};\n\nfunction getStyles(theme: GrafanaTheme2) {\n return {\n annotation: css`\n display: inline-block;\n margin-right: ${theme.spacing(1)};\n\n .fa-caret-down {\n font-size: 75%;\n padding-left: ${theme.spacing(1)};\n }\n\n .gf-form-inline .gf-form {\n margin-bottom: 0;\n }\n `,\n indicator: css`\n align-self: center;\n padding: 0 ${theme.spacing(0.5)};\n `,\n };\n}\n","import React, { useEffect, useState } from 'react';\n\nimport { AnnotationQuery, DataQuery, EventBus } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\n\nimport { AnnotationPicker } from './AnnotationPicker';\n\ninterface Props {\n events: EventBus;\n annotations: AnnotationQuery[];\n onAnnotationChanged: (annotation: AnnotationQuery) => void;\n}\n\nexport const Annotations = ({ annotations, onAnnotationChanged, events }: Props) => {\n const [visibleAnnotations, setVisibleAnnotations] = useState([]);\n useEffect(() => {\n setVisibleAnnotations(annotations.filter((annotation) => annotation.hide !== true));\n }, [annotations]);\n\n if (visibleAnnotations.length === 0) {\n return null;\n }\n\n return (\n \n {visibleAnnotations.map((annotation) => (\n
\n ))}\n
\n );\n};\n","import React from 'react';\nimport { useEffectOnce } from 'react-use';\n\nimport { sanitizeUrl } from '@grafana/data/src/text/sanitize';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { TimeRangeUpdatedEvent } from '@grafana/runtime';\nimport { DashboardLink } from '@grafana/schema';\nimport { Tooltip, useForceUpdate } from '@grafana/ui';\nimport { LINK_ICON_MAP } from 'app/features/dashboard-scene/settings/links/utils';\n\nimport { getLinkSrv } from '../../../panel/panellinks/link_srv';\nimport { DashboardModel } from '../../state';\n\nimport { DashboardLinkButton, DashboardLinksDashboard } from './DashboardLinksDashboard';\n\nexport interface Props {\n dashboard: DashboardModel;\n links: DashboardLink[];\n}\n\nexport const DashboardLinks = ({ dashboard, links }: Props) => {\n const forceUpdate = useForceUpdate();\n\n useEffectOnce(() => {\n const sub = dashboard.events.subscribe(TimeRangeUpdatedEvent, forceUpdate);\n return () => sub.unsubscribe();\n });\n\n if (!links.length) {\n return null;\n }\n\n return (\n <>\n {links.map((link: DashboardLink, index: number) => {\n const linkInfo = getLinkSrv().getAnchorInfo(link);\n const key = `${link.title}-$${index}`;\n\n if (link.type === 'dashboards') {\n return ;\n }\n\n const icon = LINK_ICON_MAP[link.icon];\n\n const linkElement = (\n \n {linkInfo.title}\n \n );\n\n return (\n \n {link.tooltip ? {linkElement} : linkElement}\n
\n );\n })}\n >\n );\n};\n","import { css } from '@emotion/css';\nimport React, { PureComponent } from 'react';\nimport { connect, MapStateToProps } from 'react-redux';\n\nimport { AnnotationQuery, DataQuery, TypedVariableModel, GrafanaTheme2 } from '@grafana/data';\nimport { DashboardLink } from '@grafana/schema';\nimport { stylesFactory, Themeable2, withTheme2 } from '@grafana/ui';\n\nimport { StoreState } from '../../../../types';\nimport { getSubMenuVariables, getVariablesState } from '../../../variables/state/selectors';\nimport { DashboardModel } from '../../state';\n\nimport { Annotations } from './Annotations';\nimport { DashboardLinks } from './DashboardLinks';\nimport { SubMenuItems } from './SubMenuItems';\n\ninterface OwnProps extends Themeable2 {\n dashboard: DashboardModel;\n links: DashboardLink[];\n annotations: AnnotationQuery[];\n}\n\ninterface ConnectedProps {\n variables: TypedVariableModel[];\n}\n\ninterface DispatchProps {}\n\ntype Props = OwnProps & ConnectedProps & DispatchProps;\n\nclass SubMenuUnConnected extends PureComponent {\n onAnnotationStateChanged = (updatedAnnotation: AnnotationQuery) => {\n // we're mutating dashboard state directly here until annotations are in Redux.\n for (let index = 0; index < this.props.dashboard.annotations.list.length; index++) {\n const annotation = this.props.dashboard.annotations.list[index];\n if (annotation.name === updatedAnnotation.name) {\n annotation.enable = !annotation.enable;\n break;\n }\n }\n this.props.dashboard.startRefresh();\n this.forceUpdate();\n };\n\n disableSubmitOnEnter = (e: React.FormEvent) => {\n e.preventDefault();\n };\n\n render() {\n const { dashboard, variables, links, annotations, theme } = this.props;\n\n const styles = getStyles(theme);\n\n if (!dashboard.isSubMenuVisible()) {\n return null;\n }\n\n const readOnlyVariables = dashboard.meta.isSnapshot ?? false;\n\n return (\n \n
\n
\n
\n {dashboard &&
}\n
\n );\n }\n}\n\nconst mapStateToProps: MapStateToProps = (state, ownProps) => {\n const { uid } = ownProps.dashboard;\n const templatingState = getVariablesState(uid, state);\n return {\n variables: getSubMenuVariables(uid, templatingState.variables),\n };\n};\n\nconst getStyles = stylesFactory((theme: GrafanaTheme2) => {\n return {\n formStyles: css`\n display: flex;\n flex-wrap: wrap;\n display: contents;\n `,\n submenu: css`\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-content: flex-start;\n align-items: flex-start;\n gap: ${theme.spacing(1)} ${theme.spacing(2)};\n padding: 0 0 ${theme.spacing(1)} 0;\n `,\n spacer: css({\n flexGrow: 1,\n }),\n };\n});\n\nexport const SubMenu = withTheme2(connect(mapStateToProps)(SubMenuUnConnected));\n\nSubMenu.displayName = 'SubMenu';\n","import { cx } from '@emotion/css';\nimport React, { PureComponent } from 'react';\nimport { connect, ConnectedProps } from 'react-redux';\n\nimport { NavModel, NavModelItem, TimeRange, PageLayoutType, locationUtil } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { config, locationService } from '@grafana/runtime';\nimport { Themeable2, withTheme2 } from '@grafana/ui';\nimport { notifyApp } from 'app/core/actions';\nimport { Page } from 'app/core/components/Page/Page';\nimport { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';\nimport { GrafanaContext, GrafanaContextType } from 'app/core/context/GrafanaContext';\nimport { createErrorNotification } from 'app/core/copy/appNotification';\nimport { getKioskMode } from 'app/core/navigation/kiosk';\nimport { GrafanaRouteComponentProps } from 'app/core/navigation/types';\nimport { getNavModel } from 'app/core/selectors/navModel';\nimport { PanelModel } from 'app/features/dashboard/state';\nimport { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';\nimport { AngularDeprecationNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationNotice';\nimport { getPageNavFromSlug, getRootContentNavModel } from 'app/features/storage/StorageFolderPage';\nimport { DashboardRoutes, KioskMode, StoreState } from 'app/types';\nimport { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events';\n\nimport { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions';\nimport { findTemplateVarChanges } from '../../variables/utils';\nimport { AddWidgetModal } from '../components/AddWidgetModal/AddWidgetModal';\nimport { DashNav } from '../components/DashNav';\nimport { DashboardFailed } from '../components/DashboardLoading/DashboardFailed';\nimport { DashboardLoading } from '../components/DashboardLoading/DashboardLoading';\nimport { DashboardPrompt } from '../components/DashboardPrompt/DashboardPrompt';\nimport { DashboardSettings } from '../components/DashboardSettings';\nimport { PanelInspector } from '../components/Inspector/PanelInspector';\nimport { PanelEditor } from '../components/PanelEditor/PanelEditor';\nimport { ShareModal } from '../components/ShareModal';\nimport { SubMenu } from '../components/SubMenu/SubMenu';\nimport { DashboardGrid } from '../dashgrid/DashboardGrid';\nimport { liveTimer } from '../dashgrid/liveTimer';\nimport { getTimeSrv } from '../services/TimeSrv';\nimport { cleanUpDashboardAndVariables } from '../state/actions';\nimport { initDashboard } from '../state/initDashboard';\n\nimport { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types';\n\nexport const mapStateToProps = (state: StoreState) => ({\n initPhase: state.dashboard.initPhase,\n initError: state.dashboard.initError,\n dashboard: state.dashboard.getModel(),\n navIndex: state.navIndex,\n});\n\nconst mapDispatchToProps = {\n initDashboard,\n cleanUpDashboardAndVariables,\n notifyApp,\n cancelVariables,\n templateVarsChangedInUrl,\n};\n\nconst connector = connect(mapStateToProps, mapDispatchToProps);\n\nexport type Props = Themeable2 &\n GrafanaRouteComponentProps &\n ConnectedProps;\n\nexport interface State {\n editPanel: PanelModel | null;\n viewPanel: PanelModel | null;\n updateScrollTop?: number;\n rememberScrollTop?: number;\n showLoadingState: boolean;\n panelNotFound: boolean;\n editPanelAccessDenied: boolean;\n scrollElement?: HTMLDivElement;\n pageNav?: NavModelItem;\n sectionNav?: NavModel;\n}\n\nexport class UnthemedDashboardPage extends PureComponent {\n declare context: GrafanaContextType;\n static contextType = GrafanaContext;\n\n private forceRouteReloadCounter = 0;\n state: State = this.getCleanState();\n\n getCleanState(): State {\n return {\n editPanel: null,\n viewPanel: null,\n showLoadingState: false,\n panelNotFound: false,\n editPanelAccessDenied: false,\n };\n }\n\n componentDidMount() {\n this.initDashboard();\n this.forceRouteReloadCounter = (this.props.history.location.state as any)?.routeReloadCounter || 0;\n }\n\n componentWillUnmount() {\n this.closeDashboard();\n }\n\n closeDashboard() {\n this.props.cleanUpDashboardAndVariables();\n this.setState(this.getCleanState());\n }\n\n initDashboard() {\n const { dashboard, match, queryParams } = this.props;\n\n if (dashboard) {\n this.closeDashboard();\n }\n\n this.props.initDashboard({\n urlSlug: match.params.slug,\n urlUid: match.params.uid,\n urlType: match.params.type,\n urlFolderUid: queryParams.folderUid,\n panelType: queryParams.panelType,\n routeName: this.props.route.routeName,\n fixUrl: true,\n accessToken: match.params.accessToken,\n keybindingSrv: this.context.keybindings,\n });\n\n // small delay to start live updates\n setTimeout(this.updateLiveTimer, 250);\n }\n\n componentDidUpdate(prevProps: Props, prevState: State) {\n const { dashboard, match, templateVarsChangedInUrl } = this.props;\n const routeReloadCounter = (this.props.history.location.state as any)?.routeReloadCounter;\n\n if (!dashboard) {\n return;\n }\n\n if (\n prevProps.match.params.uid !== match.params.uid ||\n (routeReloadCounter !== undefined && this.forceRouteReloadCounter !== routeReloadCounter)\n ) {\n this.initDashboard();\n this.forceRouteReloadCounter = routeReloadCounter;\n return;\n }\n\n if (prevProps.location.search !== this.props.location.search) {\n const prevUrlParams = prevProps.queryParams;\n const urlParams = this.props.queryParams;\n\n if (urlParams?.from !== prevUrlParams?.from || urlParams?.to !== prevUrlParams?.to) {\n getTimeSrv().updateTimeRangeFromUrl();\n this.updateLiveTimer();\n }\n\n if (!prevUrlParams?.refresh && urlParams?.refresh) {\n getTimeSrv().setAutoRefresh(urlParams.refresh);\n }\n\n const templateVarChanges = findTemplateVarChanges(this.props.queryParams, prevProps.queryParams);\n\n if (templateVarChanges) {\n templateVarsChangedInUrl(dashboard.uid, templateVarChanges);\n }\n }\n\n // entering edit mode\n if (this.state.editPanel && !prevState.editPanel) {\n dashboardWatcher.setEditingState(true);\n\n // Some panels need to be notified when entering edit mode\n this.props.dashboard?.events.publish(new PanelEditEnteredEvent(this.state.editPanel.id));\n }\n\n // leaving edit mode\n if (!this.state.editPanel && prevState.editPanel) {\n dashboardWatcher.setEditingState(false);\n\n // Some panels need kicked when leaving edit mode\n this.props.dashboard?.events.publish(new PanelEditExitedEvent(prevState.editPanel.id));\n }\n\n if (this.state.editPanelAccessDenied) {\n this.props.notifyApp(createErrorNotification('Permission to edit panel denied'));\n locationService.partial({ editPanel: null });\n }\n\n if (this.state.panelNotFound) {\n this.props.notifyApp(createErrorNotification(`Panel not found`));\n locationService.partial({ editPanel: null, viewPanel: null });\n }\n }\n\n updateLiveTimer = () => {\n let tr: TimeRange | undefined = undefined;\n if (this.props.dashboard?.liveNow) {\n tr = getTimeSrv().timeRange();\n }\n liveTimer.setLiveTimeRange(tr);\n };\n\n static getDerivedStateFromProps(props: Props, state: State) {\n const { dashboard, queryParams } = props;\n\n const urlEditPanelId = queryParams.editPanel;\n const urlViewPanelId = queryParams.viewPanel;\n\n if (!dashboard) {\n return state;\n }\n\n const updatedState = { ...state };\n\n // Entering edit mode\n if (!state.editPanel && urlEditPanelId) {\n const panel = dashboard.getPanelByUrlId(urlEditPanelId);\n if (panel) {\n if (dashboard.canEditPanel(panel)) {\n updatedState.editPanel = panel;\n updatedState.rememberScrollTop = state.scrollElement?.scrollTop;\n } else {\n updatedState.editPanelAccessDenied = true;\n }\n } else {\n updatedState.panelNotFound = true;\n }\n }\n // Leaving edit mode\n else if (state.editPanel && !urlEditPanelId) {\n updatedState.editPanel = null;\n updatedState.updateScrollTop = state.rememberScrollTop;\n }\n\n // Entering view mode\n if (!state.viewPanel && urlViewPanelId) {\n const panel = dashboard.getPanelByUrlId(urlViewPanelId);\n if (panel) {\n // This mutable state feels wrong to have in getDerivedStateFromProps\n // Should move this state out of dashboard in the future\n dashboard.initViewPanel(panel);\n updatedState.viewPanel = panel;\n updatedState.rememberScrollTop = state.scrollElement?.scrollTop;\n updatedState.updateScrollTop = 0;\n } else {\n updatedState.panelNotFound = true;\n }\n }\n // Leaving view mode\n else if (state.viewPanel && !urlViewPanelId) {\n // This mutable state feels wrong to have in getDerivedStateFromProps\n // Should move this state out of dashboard in the future\n dashboard.exitViewPanel(state.viewPanel);\n updatedState.viewPanel = null;\n updatedState.updateScrollTop = state.rememberScrollTop;\n }\n\n // if we removed url edit state, clear any panel not found state\n if (state.panelNotFound || (state.editPanelAccessDenied && !urlEditPanelId)) {\n updatedState.panelNotFound = false;\n updatedState.editPanelAccessDenied = false;\n }\n\n return updateStatePageNavFromProps(props, updatedState);\n }\n\n setScrollRef = (scrollElement: HTMLDivElement): void => {\n this.setState({ scrollElement });\n };\n\n getInspectPanel() {\n const { dashboard, queryParams } = this.props;\n\n const inspectPanelId = queryParams.inspect;\n\n if (!dashboard || !inspectPanelId) {\n return null;\n }\n\n const inspectPanel = dashboard.getPanelById(parseInt(inspectPanelId, 10));\n\n // cannot inspect panels plugin is not already loaded\n if (!inspectPanel) {\n return null;\n }\n\n return inspectPanel;\n }\n\n onCloseShareModal = () => {\n locationService.partial({ shareView: null });\n };\n\n render() {\n const { dashboard, initError, queryParams } = this.props;\n const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state;\n const kioskMode = getKioskMode(this.props.queryParams);\n\n if (!dashboard || !pageNav || !sectionNav) {\n return ;\n }\n\n const inspectPanel = this.getInspectPanel();\n const showSubMenu = !editPanel && !kioskMode && !this.props.queryParams.editview;\n\n const showToolbar = kioskMode !== KioskMode.Full && !queryParams.editview;\n\n const pageClassName = cx({\n 'panel-in-fullscreen': Boolean(viewPanel),\n 'page-hidden': Boolean(queryParams.editview || editPanel),\n });\n\n if (dashboard.meta.dashboardNotFound) {\n return (\n \n \n \n );\n }\n\n return (\n <>\n \n {showToolbar && (\n \n )}\n \n {initError && }\n {showSubMenu && (\n \n )}\n {config.featureToggles.angularDeprecationUI && dashboard.hasAngularPlugins() && dashboard.uid !== null && (\n \n )}\n \n\n {inspectPanel && }\n {queryParams.shareView && }\n \n {editPanel && (\n \n )}\n {queryParams.editview && (\n \n )}\n {queryParams.addWidget && config.featureToggles.vizAndWidgetSplit && }\n >\n );\n }\n}\n\nfunction updateStatePageNavFromProps(props: Props, state: State): State {\n const { dashboard, navIndex } = props;\n\n if (!dashboard) {\n return state;\n }\n\n let pageNav = state.pageNav;\n let sectionNav = state.sectionNav;\n\n if (!pageNav || dashboard.title !== pageNav.text || dashboard.meta.folderUrl !== pageNav.parentItem?.url) {\n pageNav = {\n text: dashboard.title,\n url: locationUtil.getUrlForPartial(props.history.location, {\n editview: null,\n editPanel: null,\n viewPanel: null,\n }),\n };\n }\n\n const { folderUid } = dashboard.meta;\n if (folderUid && pageNav) {\n const folderNavModel = getNavModel(navIndex, `folder-dashboards-${folderUid}`).main;\n // If the folder hasn't loaded (maybe user doesn't have permission on it?) then\n // don't show the \"page not found\" breadcrumb\n if (folderNavModel.id !== 'not-found') {\n pageNav = {\n ...pageNav,\n parentItem: folderNavModel,\n };\n }\n }\n\n if (props.route.routeName === DashboardRoutes.Path) {\n sectionNav = getRootContentNavModel();\n const pageNav = getPageNavFromSlug(props.match.params.slug!);\n if (pageNav?.parentItem) {\n pageNav.parentItem = pageNav.parentItem;\n }\n } else {\n sectionNav = getNavModel(props.navIndex, 'dashboards/browse');\n }\n\n if (state.editPanel || state.viewPanel) {\n pageNav = {\n ...pageNav,\n text: `${state.editPanel ? 'Edit' : 'View'} panel`,\n parentItem: pageNav,\n url: undefined,\n };\n }\n\n if (state.pageNav === pageNav && state.sectionNav === sectionNav) {\n return state;\n }\n\n return {\n ...state,\n pageNav,\n sectionNav,\n };\n}\n\nexport const DashboardPage = withTheme2(UnthemedDashboardPage);\nDashboardPage.displayName = 'DashboardPage';\nexport default connector(DashboardPage);\n","import React from 'react';\nimport { useAsync } from 'react-use';\n\nimport { config } from '@grafana/runtime';\nimport { GrafanaRouteComponentProps } from 'app/core/navigation/types';\nimport DashboardScenePage from 'app/features/dashboard-scene/pages/DashboardScenePage';\nimport { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager';\nimport { DashboardRoutes } from 'app/types';\n\nimport DashboardPage from './DashboardPage';\nimport { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types';\n\nexport type DashboardPageProxyProps = GrafanaRouteComponentProps<\n DashboardPageRouteParams,\n DashboardPageRouteSearchParams\n>;\n\n// This proxy component is used for Dashboard -> Scenes migration.\n// It will render DashboardScenePage if the user is only allowed to view the dashboard.\nfunction DashboardPageProxy(props: DashboardPageProxyProps) {\n if (config.featureToggles.dashboardScene || props.queryParams.scenes) {\n return ;\n }\n\n const stateManager = getDashboardScenePageStateManager();\n const isScenesSupportedRoute = Boolean(\n props.route.routeName === DashboardRoutes.Home ||\n (props.route.routeName === DashboardRoutes.Normal && props.match.params.uid)\n );\n\n // We pre-fetch dashboard to render dashboard page component depending on dashboard permissions.\n // To avoid querying single dashboard multiple times, stateManager.fetchDashboard uses a simple, short-lived cache.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const dashboard = useAsync(async () => {\n if (props.match.params.type === 'snapshot') {\n return null;\n }\n\n return stateManager.fetchDashboard({\n route: props.route.routeName as DashboardRoutes,\n uid: props.match.params.uid ?? '',\n });\n }, [props.match.params.uid, props.route.routeName]);\n\n if (!config.featureToggles.dashboardSceneForViewers) {\n return ;\n }\n\n if (dashboard.loading) {\n return null;\n }\n\n if (\n dashboard.value &&\n !(dashboard.value.meta.canEdit || dashboard.value.meta.canMakeEditable) &&\n isScenesSupportedRoute\n ) {\n return ;\n } else {\n return ;\n }\n}\n\nexport default DashboardPageProxy;\n","import { DataFrame, dataFrameFromJSON, DataFrameJSON, getDisplayProcessor } from '@grafana/data';\nimport { config, getBackendSrv } from '@grafana/runtime';\nimport { backendSrv } from 'app/core/services/backend_srv';\n\nimport { UploadResponse, StorageInfo, ItemOptions, WriteValueRequest, WriteValueResponse } from './types';\n\n// Likely should be built into the search interface!\nexport interface GrafanaStorage {\n get: (path: string) => Promise;\n list: (path: string) => Promise;\n upload: (folder: string, file: File, overwriteExistingFile: boolean) => Promise;\n createFolder: (path: string) => Promise<{ error?: string }>;\n delete: (path: { isFolder: boolean; path: string }) => Promise<{ error?: string }>;\n\n /** Admin only */\n getConfig: () => Promise;\n\n /** Called before save */\n getOptions: (path: string) => Promise;\n\n /** Saves dashboards */\n write: (path: string, options: WriteValueRequest) => Promise;\n}\n\nclass SimpleStorage implements GrafanaStorage {\n constructor() {}\n\n async get(path: string): Promise {\n const storagePath = `api/storage/read/${path}`.replace('//', '/');\n return getBackendSrv().get(storagePath);\n }\n\n async list(path: string): Promise {\n let url = 'api/storage/list/';\n if (path) {\n url += path + '/';\n }\n const rsp = await getBackendSrv().get(url);\n if (rsp?.data) {\n const f = dataFrameFromJSON(rsp);\n for (const field of f.fields) {\n field.display = getDisplayProcessor({ field, theme: config.theme2 });\n }\n return f;\n }\n return undefined;\n }\n\n async createFolder(path: string): Promise<{ error?: string }> {\n const res = await getBackendSrv().post<{ success: boolean; message: string }>(\n '/api/storage/createFolder',\n JSON.stringify({ path })\n );\n\n if (!res.success) {\n return {\n error: res.message ?? 'unknown error',\n };\n }\n\n return {};\n }\n\n async deleteFolder(req: { path: string; force: boolean }): Promise<{ error?: string }> {\n const res = await getBackendSrv().post<{ success: boolean; message: string }>(\n `/api/storage/deleteFolder`,\n JSON.stringify(req)\n );\n\n if (!res.success) {\n return {\n error: res.message ?? 'unknown error',\n };\n }\n\n return {};\n }\n\n async deleteFile(req: { path: string }): Promise<{ error?: string }> {\n const res = await getBackendSrv().post<{ success: boolean; message: string }>(`/api/storage/delete/${req.path}`);\n\n if (!res.success) {\n return {\n error: res.message ?? 'unknown error',\n };\n }\n\n return {};\n }\n\n async delete(req: { isFolder: boolean; path: string }): Promise<{ error?: string }> {\n return req.isFolder ? this.deleteFolder({ path: req.path, force: true }) : this.deleteFile({ path: req.path });\n }\n\n async upload(folder: string, file: File, overwriteExistingFile: boolean): Promise {\n const formData = new FormData();\n formData.append('folder', folder);\n formData.append('file', file);\n formData.append('overwriteExistingFile', String(overwriteExistingFile));\n const res = await fetch('/api/storage/upload', {\n method: 'POST',\n body: formData,\n });\n\n let body = await res.json();\n if (!body) {\n body = {};\n }\n body.status = res.status;\n body.statusText = res.statusText;\n if (res.status !== 200 && !body.err) {\n body.err = true;\n }\n return body;\n }\n\n async write(path: string, options: WriteValueRequest): Promise {\n return backendSrv.post(`/api/storage/write/${path}`, options);\n }\n\n async getConfig() {\n return getBackendSrv().get('/api/storage/config');\n }\n\n async getOptions(path: string) {\n return getBackendSrv().get(`/api/storage/options/${path}`);\n }\n}\n\nexport function filenameAlreadyExists(folderName: string, fileNames: string[]) {\n const lowerCase = folderName.toLowerCase();\n const trimmedLowerCase = lowerCase.trim();\n const existingTrimmedLowerCaseNames = fileNames.map((f) => f.trim().toLowerCase());\n\n return existingTrimmedLowerCaseNames.includes(trimmedLowerCase);\n}\n\nlet storage: GrafanaStorage | undefined;\n\nexport function getGrafanaStorage() {\n if (!storage) {\n storage = new SimpleStorage();\n }\n return storage;\n}\n"],"names":["getDefaultCondition","getAlertingValidationMessage","transformations","targets","datasourceSrv","datasource","alertingNotSupported","templateVariablesNotSupported","target","dsRef","ds","DashboardScenePage","match","route","queryParams","history","stateManager","dashboard","isLoading","loadError","routeReloadCounter","PageLoader","getKioskMode","LOCAL_STORAGE_KEY_PREFIX","localStorageKey","dashboardUid","AngularDeprecationNotice","LocalStorageValueProvider","isDismissed","onDismiss","Alert","StorageFolderPage","props","slug","listing","childRoot","pageNav","getPageNavFromSlug","renderListing","item","name","isFolder","isDash","url","navModel","getRootContentNavModel","parts","pageNavs","lastPageNav","AddWidgetModal","styles","getStyles","searchQuery","setSearchQuery","state","widgetsList","filteredWidgetsTypes","Modal","Input","e","CustomScrollbar","plugin","index","VizTypePickerPlugin","id","theme","SaveLibraryPanelModal","panel","folderUid","isUnsavedPrompt","onConfirm","onDiscard","searchString","setSearchString","dashState","searchHits","dash","filteredDashboards","setFilteredDashboards","useDebounce","dashName","saveLibraryPanel","usePanelSave","discardAndClose","title","i","Button","initPanelEditor","sourcePanel","dispatch","discardPanelChanges","getStore","getPanel","updateDuplicateLibraryPanels","modifiedPanel","modifiedSaveModel","skipPanelUpdate","pluginChanged","panelToUpdate","exitPanelEditor","getSourcePanel","shouldDiscardChanges","hasPanelChangedInPanelEdit","panelTypeChanged","updatePanelEditorUIState","uiState","nextState","store","error","UnsavedChangesModal","onSaveSuccess","SaveDashboardButton","DashboardPrompt","setState","original","originalPath","showModal","hideModal","ModalsContext","timeoutId","savedEventUnsub","handleUnload","event","ignoreChanges","hasChanges","onHistoryBlock","location","panelInEdit","search","moveToBlockedLocationAfterReactStateUpdate","current","canSave","fromScript","fromFile","cleanDashboardFromIgnoredChanges","dashData","model","DashboardModel","variable","currentClean","originalClean","currentTimepicker","originalTimepicker","currentJson","originalJson","SnapshotTab","ShowMessage","SupportSnapshotService","StateManagerBase","value","markdownText","maxLen","snapshotText","panelTitle","blob","fileName","k","randomize","snapshot","config","snapshotUpdate","snapshotSize","scene","oldModel","ex","HelpWizard","onClose","service","currentTab","loading","options","showMessage","tabs","hasSupportBundleAccess","Drawer","Stack","TabsBar","t","Tab","Field","Select","ClipboardButton","height","CodeEditor","usePanelLatestData","checkSchema","querySubscription","latestData","setLatestData","lastRev","lastUpdate","data","now","InspectMetadataTab","metadataDatasource","InspectContent","isDataLoading","dataOptions","defaultTab","onDataOptionsChange","setCurrentTab","errors","getErrors","activeTab","formatStats","tab","InspectDataTab","InspectJSONTab","InspectErrorTab","InspectStatsTab","QueryInspector","request","queryCount","requestTime","formatted","PanelInspectorUnconnected","setDataOptions","hasError","metaDs","mapStateToProps","panelState","PanelInspector","PickerRenderer","PickerToRender","PickerLabel","labelOrName","elementId","Tooltip","selectors","SubMenuItems","variables","readOnly","visibleVariables","setVisibleVariables","getPanelPluginWithFallback","panelType","VisualizationButton","isPanelOptionsVisible","isVizPickerOpen","onToggleOpen","onToggleOptionsPane","ButtonGroup","ToolbarButton","ChangeLibraryPanelModal","isLibraryPanel","body","ConfirmModal","PanelLibraryOptionsGroup","isWidget","showingAddPanelModal","setShowingAddPanelModal","changeToPanel","setChangeToPanel","panelFilter","setPanelFilter","onPanelFilterChange","plugins","p","useLibraryPanel","onAddToPanelLibrary","onDismissChangeToPanel","PanelTypeFilter","LibraryPanelsView","AddLibraryPanelModal","VisualizationSelectPane","isWidgetEnabled","tabKey","listMode","setListMode","useLocalStorage","searchRef","onVizChange","pluginChange","onCloseVizPicker","radioOptions","radioOptionsWidgetFlow","FilterInput","RadioButtonGroup","VizTypePicker","VisualizationSuggestions","OptionsPane","onFieldConfigsChange","onPanelOptionsChanged","onPanelConfigChange","instanceState","OptionsPaneOptions","InfoMode","PanelHeaderCorner","markdown","interpolatedMarkdown","markedInterpolatedMarkdown","links","link","idx","infoMode","content","onClick","className","ariaLabel","PanelEditorTableView","width","setOptions","timeSrv","sub","timeData","errorMessage","PanelChrome","innerWidth","innerHeight","PanelRenderer","StateHistory","panelId","onRefresh","items","alertDef","stateHistoryItems","ConfirmButton","UnThemedTestRuleResult","formattedJson","prevState","allNodesExpanded","collapse","expand","dashPanel","payload","testRuleResponse","clearButton","LoadingPlaceholder","openNodes","JSONFormatter","TestRuleResult","UnConnectedAlertTab","prop","prevProps","angularPanelComponent","scope","loader","template","scopeProps","validationMessage","EventBus","alert","hasTransformations","PanelNotSupported","element","EmptyListCTA","mapDispatchToProps","AlertTab","NewRuleFromPanelButton","templating","formValues","ruleFormUrl","PanelAlertTabContent","rules","usePanelCombinedRules","permissions","canCreateRules","RulesTable","PanelAlertTab","otherProps","PanelEditorQueries","dataSource","datasourceSettings","lastUsedDatasource","QueryGroup","PanelEditorTabs","onChangeTab","forceUpdate","useForceUpdate","instrumentedOnChangeTab","eventName","eventSubs","Subscription","renderAlertTab","getCounter","TabContent","AlertTabIndex","TransformationsEditor","ownProps","connector","PanelEditorUnconnected","SaveDashboardDrawer","configKey","mode","initDone","isOnlyPanel","tableViewEnabled","panelSize","DashboardPanel","panelPane","SplitPaneWrapper","size","updateTimeZoneForSession","DashNavTimeControls","editorActions","UnlinkModal","sectionNav","AppChromeUpdate","ToolbarButtonRow","PanelEditor","stylesFactory","paneSpacing","AnnotationPicker","annotation","events","onEnabledChanged","setLoading","onCancel","started","stopped","InlineFieldRow","InlineField","LoadingIndicator","Annotations","annotations","onAnnotationChanged","visibleAnnotations","setVisibleAnnotations","DashboardLinks","useEffectOnce","linkInfo","key","DashboardLinksDashboard","icon","linkElement","sanitize","SubMenuUnConnected","updatedAnnotation","readOnlyVariables","uid","templatingState","SubMenu","UnthemedDashboardPage","tr","liveTimer","scrollElement","templateVarsChangedInUrl","prevUrlParams","urlParams","templateVarChanges","dashboardWatcher","urlEditPanelId","urlViewPanelId","updatedState","updateStatePageNavFromProps","inspectPanelId","inspectPanel","initError","editPanel","viewPanel","updateScrollTop","kioskMode","DashboardLoading","showSubMenu","showToolbar","pageClassName","EntityNotFound","DashNav","DashboardFailed","DashboardGrid","ShareModal","DashboardSettings","GrafanaContext","navIndex","folderNavModel","DashboardPage","DashboardPageProxy","isScenesSupportedRoute","SimpleStorage","path","storagePath","rsp","f","field","res","req","folder","file","overwriteExistingFile","formData","filenameAlreadyExists","folderName","fileNames","trimmedLowerCase","storage","getGrafanaStorage"],"sourceRoot":""}