mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Basics for proxying viewers to a dashboard scene
This commit is contained in:
@@ -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<DashboardPageRouteParams> {}
|
||||
|
||||
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 (
|
||||
|
||||
@@ -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<DashboardSc
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
const rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid);
|
||||
let rsp: DashboardDTO | undefined;
|
||||
|
||||
if (rsp.dashboard) {
|
||||
if (uid === DashboardRoutes.Home) {
|
||||
rsp = await getBackendSrv().get('/api/dashboards/home');
|
||||
|
||||
// TODO
|
||||
// if user specified a custom home dashboard redirect to that
|
||||
// if (rsp?.redirectUri) {
|
||||
// const newUrl = locationUtil.stripBaseFromUrl(rsp.redirectUri);
|
||||
// locationService.replace(newUrl);
|
||||
// }
|
||||
|
||||
if (rsp?.meta) {
|
||||
rsp.meta.canSave = false;
|
||||
rsp.meta.canShare = false;
|
||||
rsp.meta.canStar = false;
|
||||
}
|
||||
} else {
|
||||
rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid);
|
||||
}
|
||||
|
||||
if (rsp?.dashboard) {
|
||||
const scene = transformSaveModelToScene(rsp);
|
||||
|
||||
if (uid === DashboardRoutes.Home) {
|
||||
scene.isHomeDashboard = true;
|
||||
}
|
||||
|
||||
this.cache[uid] = scene;
|
||||
return scene;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import {
|
||||
SceneObjectStateChangedEvent,
|
||||
sceneUtils,
|
||||
} from '@grafana/scenes';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
import { AccessControlAction, DashboardMeta } from 'app/types';
|
||||
|
||||
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
|
||||
import { SaveDashboardDrawer } from '../serialization/SaveDashboardDrawer';
|
||||
@@ -60,6 +61,7 @@ export interface DashboardSceneState extends SceneObjectState {
|
||||
export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
static Component = DashboardSceneRenderer;
|
||||
|
||||
private _isHomeDashboard = false;
|
||||
/**
|
||||
* Handles url sync
|
||||
*/
|
||||
@@ -250,7 +252,20 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DashboardS
|
||||
const pageNav = model.getPageNav(location);
|
||||
const bodyToRender = model.getBodyToRender(viewPanelId);
|
||||
|
||||
const hasControls = model.canEditDashboard() && controls;
|
||||
|
||||
return (
|
||||
<Page navId="scenes" pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||
<CustomScrollbar autoHeightMin={'100%'}>
|
||||
<div className={styles.canvasContent}>
|
||||
<NavToolbarActions dashboard={model} />
|
||||
{controls && (
|
||||
|
||||
{hasControls && (
|
||||
<div className={styles.controls}>
|
||||
{controls.map((control) => (
|
||||
<control.Component key={control.state.key} model={control} />
|
||||
@@ -30,7 +33,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
||||
<SceneDebugger scene={model} key={'scene-debugger'} />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.body}>
|
||||
<div className={cx(styles.body, !hasControls && styles.bodyNoControls)}>
|
||||
<bodyToRender.Component model={bodyToRender} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -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',
|
||||
|
||||
@@ -79,36 +79,38 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
|
||||
}
|
||||
|
||||
if (!isEditing) {
|
||||
// TODO check permissions
|
||||
toolbarActions.push(
|
||||
<Button
|
||||
onClick={dashboard.onEnterEditMode}
|
||||
tooltip="Enter edit mode"
|
||||
key="edit"
|
||||
variant="primary"
|
||||
icon="pen"
|
||||
fill="text"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
);
|
||||
if (dashboard.canEditDashboard()) {
|
||||
toolbarActions.push(
|
||||
<Button
|
||||
onClick={dashboard.onEnterEditMode}
|
||||
tooltip="Enter edit mode"
|
||||
key="edit"
|
||||
variant="primary"
|
||||
icon="pen"
|
||||
fill="text"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO check permissions
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onSave} tooltip="Save as copy" fill="text" key="save-as">
|
||||
Save as
|
||||
</Button>
|
||||
);
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onDiscard} tooltip="Save changes" fill="text" key="discard" variant="destructive">
|
||||
Discard
|
||||
</Button>
|
||||
);
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onSave} tooltip="Save changes" key="save" disabled={!isDirty}>
|
||||
Save
|
||||
</Button>
|
||||
);
|
||||
if (dashboard.canEditDashboard()) {
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onSave} tooltip="Save as copy" fill="text" key="save-as">
|
||||
Save as
|
||||
</Button>
|
||||
);
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onDiscard} tooltip="Save changes" fill="text" key="discard" variant="destructive">
|
||||
Discard
|
||||
</Button>
|
||||
);
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onSave} tooltip="Save changes" key="save" disabled={!isDirty}>
|
||||
Save
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <AppChromeUpdate actions={toolbarActions} />;
|
||||
|
||||
@@ -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`),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<DashboardPageRouteParams, DashboardPageRouteSearchParams>;
|
||||
|
||||
// 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 <DashboardPage {...props} />;
|
||||
} else {
|
||||
return <DashboardScenePage {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return <DashboardPage {...props} />;
|
||||
}
|
||||
|
||||
export default DashboardPageProxy;
|
||||
21
public/app/features/dashboard/containers/types.ts
Normal file
21
public/app/features/dashboard/containers/types.ts
Normal file
@@ -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;
|
||||
};
|
||||
@@ -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')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user