From bbf77765050ee4184a9df14a2125ed7b55e1c6ed Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 18 Oct 2023 12:40:34 +0200 Subject: [PATCH] Basics for proxying viewers to a dashboard scene --- .../pages/DashboardScenePage.tsx | 14 +++-- .../pages/DashboardScenePageStateManager.ts | 30 +++++++++- .../dashboard-scene/scene/DashboardScene.tsx | 19 +++++- .../scene/DashboardSceneRenderer.tsx | 12 +++- .../scene/NavToolbarActions.tsx | 60 ++++++++++--------- .../scene/PanelMenuBehavior.tsx | 28 +++++---- .../dashboard/containers/DashboardPage.tsx | 22 +------ .../containers/DashboardPageProxy.tsx | 28 +++++++++ .../features/dashboard/containers/types.ts | 21 +++++++ public/app/routes/routes.tsx | 4 +- 10 files changed, 162 insertions(+), 76 deletions(-) create mode 100644 public/app/features/dashboard/containers/DashboardPageProxy.tsx create mode 100644 public/app/features/dashboard/containers/types.ts diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx index 5c27e377406..1ab9569cb1f 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx @@ -5,22 +5,28 @@ import { PageLayoutType } from '@grafana/data'; import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { DashboardPageRouteParams } from 'app/features/dashboard/containers/types'; +import { DashboardRoutes } from 'app/types'; import { getDashboardScenePageStateManager } from './DashboardScenePageStateManager'; -export interface Props extends GrafanaRouteComponentProps<{ uid: string }> {} +export interface Props extends GrafanaRouteComponentProps {} -export function DashboardScenePage({ match }: Props) { +export function DashboardScenePage({ match, route }: Props) { const stateManager = getDashboardScenePageStateManager(); const { dashboard, isLoading, loadError } = stateManager.useState(); useEffect(() => { - stateManager.loadDashboard(match.params.uid); + if (route.routeName === DashboardRoutes.Home) { + stateManager.loadDashboard(route.routeName); + } else { + stateManager.loadDashboard(match.params.uid); + } return () => { stateManager.clearState(); }; - }, [stateManager, match.params.uid]); + }, [stateManager, match.params.uid, route.routeName]); if (!dashboard) { return ( diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 78faff65731..190ebd68899 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1,6 +1,8 @@ +import { getBackendSrv } from '@grafana/runtime'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { DashboardDTO, DashboardRoutes } from 'app/types'; import { buildPanelEditScene, PanelEditor } from '../panel-edit/PanelEditor'; import { DashboardScene } from '../scene/DashboardScene'; @@ -55,10 +57,34 @@ export class DashboardScenePageStateManager extends StateManagerBase { static Component = DashboardSceneRenderer; + private _isHomeDashboard = false; /** * Handles url sync */ @@ -250,7 +252,20 @@ export class DashboardScene extends SceneObjectBase { }; } + public set isHomeDashboard(value: boolean) { + this._isHomeDashboard = value; + } + canEditDashboard() { - return Boolean(this.state.meta.canEdit || this.state.meta.canMakeEditable); + const { meta } = this.state; + + // Default home dash is not editable. + if (this._isHomeDashboard) { + return false; + } + + return ( + contextSrv.hasPermission(AccessControlAction.DashboardsWrite) && Boolean(meta.canEdit || meta.canMakeEditable) + ); } } diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 63bb7df05e6..f7459a8a0c7 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import React from 'react'; import { useLocation } from 'react-router-dom'; @@ -17,12 +17,15 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps
- {controls && ( + + {hasControls && (
{controls.map((control) => ( @@ -30,7 +33,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps
)} -
+
@@ -57,6 +60,9 @@ function getStyles(theme: GrafanaTheme2) { gap: '8px', marginBottom: theme.spacing(2), }), + bodyNoControls: css({ + paddingTop: theme.spacing(2), + }), controls: css({ display: 'flex', flexWrap: 'wrap', diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index a4db6e15024..6f5b4285c67 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -79,36 +79,38 @@ export const NavToolbarActions = React.memo(({ dashboard }) => { } if (!isEditing) { - // TODO check permissions - toolbarActions.push( - - ); + if (dashboard.canEditDashboard()) { + toolbarActions.push( + + ); + } } else { - // TODO check permissions - toolbarActions.push( - - ); - toolbarActions.push( - - ); - toolbarActions.push( - - ); + if (dashboard.canEditDashboard()) { + toolbarActions.push( + + ); + toolbarActions.push( + + ); + toolbarActions.push( + + ); + } } return ; diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index aa03646e03a..1f601138cbb 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -35,19 +35,21 @@ export function panelMenuBehavior(menu: VizPanelMenu) { href: locationUtil.getUrlForPartial(location, { viewPanel: panel.state.key }), }); - // We could check isEditing here but I kind of think this should always be in the menu, - // and going into panel edit should make the dashboard go into edit mode is it's not already - items.push({ - text: t('panel.header-menu.edit', `Edit`), - iconClassName: 'eye', - shortcut: 'v', - onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'edit' }), - href: getDashboardUrl({ - uid: dashboard.state.uid, - subPath: `/panel-edit/${panelId}`, - currentQueryParams: location.search, - }), - }); + if (dashboard.canEditDashboard()) { + // We could check isEditing here but I kind of think this should always be in the menu, + // and going into panel edit should make the dashboard go into edit mode is it's not already + items.push({ + text: t('panel.header-menu.edit', `Edit`), + iconClassName: 'eye', + shortcut: 'v', + onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'edit' }), + href: getDashboardUrl({ + uid: dashboard.state.uid, + subPath: `/panel-edit/${panelId}`, + currentQueryParams: location.search, + }), + }); + } items.push({ text: t('panel.header-menu.share', `Share`), diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index b2975758d84..8e20cf9a98b 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -40,27 +40,7 @@ import { cleanUpDashboardAndVariables } from '../state/actions'; import { initDashboard } from '../state/initDashboard'; import { calculateNewPanelGridPos } from '../utils/panel'; -export interface DashboardPageRouteParams { - uid?: string; - type?: string; - slug?: string; - accessToken?: string; -} - -export type DashboardPageRouteSearchParams = { - tab?: string; - folderUid?: string; - editPanel?: string; - viewPanel?: string; - editview?: string; - addWidget?: boolean; - panelType?: string; - inspect?: string; - from?: string; - to?: string; - refresh?: string; - kiosk?: string | true; -}; +import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types'; export const mapStateToProps = (state: StoreState) => ({ initPhase: state.dashboard.initPhase, diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx new file mode 100644 index 00000000000..50ae81b66ac --- /dev/null +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import DashboardScenePage from 'app/features/dashboard-scene/pages/DashboardScenePage'; +import { AccessControlAction } from 'app/types'; + +import DashboardPage from './DashboardPage'; +import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types'; + +type Props = GrafanaRouteComponentProps; + +// This proxy component is used for Dashboard -> Scenes migration. +// It will render DashboardScenePage if user does not have write permissions to a dashboard. +function DashboardPageProxy(props: Props) { + if (config.featureToggles.dashboardSceneForViewers) { + if (contextSrv.hasPermission(AccessControlAction.DashboardsWrite)) { + return ; + } else { + return ; + } + } + + return ; +} + +export default DashboardPageProxy; diff --git a/public/app/features/dashboard/containers/types.ts b/public/app/features/dashboard/containers/types.ts new file mode 100644 index 00000000000..247e187e73b --- /dev/null +++ b/public/app/features/dashboard/containers/types.ts @@ -0,0 +1,21 @@ +export interface DashboardPageRouteParams { + uid: string; + type?: string; + slug?: string; + accessToken?: string; +} + +export type DashboardPageRouteSearchParams = { + tab?: string; + folderUid?: string; + editPanel?: string; + viewPanel?: string; + editview?: string; + addWidget?: boolean; + panelType?: string; + inspect?: string; + from?: string; + to?: string; + refresh?: string; + kiosk?: string | true; +}; diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index b46e9e8f86d..650795045c6 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -36,7 +36,7 @@ export function getAppRoutes(): RouteDescriptor[] { pageClass: 'page-dashboard', routeName: DashboardRoutes.Home, component: SafeDynamicImport( - () => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage') + () => import(/* webpackChunkName: "DashboardPageProxy" */ '../features/dashboard/containers/DashboardPageProxy') ), }, { @@ -44,7 +44,7 @@ export function getAppRoutes(): RouteDescriptor[] { pageClass: 'page-dashboard', routeName: DashboardRoutes.Normal, component: SafeDynamicImport( - () => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage') + () => import(/* webpackChunkName: "DashboardPageProxy" */ '../features/dashboard/containers/DashboardPageProxy') ), }, {