mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: A unique page for public dashboards (#60744)
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
d19d8c6625
commit
2505f112f5
@ -58,6 +58,7 @@ export const PageToolbar: FC<Props> = React.memo(
|
|||||||
styles.toolbar,
|
styles.toolbar,
|
||||||
{
|
{
|
||||||
['page-toolbar--fullscreen']: isFullscreen,
|
['page-toolbar--fullscreen']: isFullscreen,
|
||||||
|
[styles.noPageIcon]: !pageIcon,
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
@ -160,6 +161,15 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
gap: ${theme.spacing(2)};
|
gap: ${theme.spacing(2)};
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: ${theme.spacing(1.5, 2)};
|
padding: ${theme.spacing(1.5, 2)};
|
||||||
|
|
||||||
|
${theme.breakpoints.down('md')} {
|
||||||
|
padding-left: 53px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
noPageIcon: css`
|
||||||
|
${theme.breakpoints.down('md')} {
|
||||||
|
padding-left: ${theme.spacing(2)};
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
leftWrapper: css`
|
leftWrapper: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -44,6 +44,7 @@ func (api *Api) ViewPublicDashboard(c *models.ReqContext) response.Response {
|
|||||||
PublicDashboardAccessToken: pubdash.AccessToken,
|
PublicDashboardAccessToken: pubdash.AccessToken,
|
||||||
PublicDashboardUID: pubdash.Uid,
|
PublicDashboardUID: pubdash.Uid,
|
||||||
}
|
}
|
||||||
|
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)
|
||||||
|
|
||||||
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
|
dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
|
|
||||||
if (!config.featureToggles.topnav || config.isPublicDashboardView) {
|
if (!config.featureToggles.topnav) {
|
||||||
return <main className="main-view">{children}</main>;
|
return <main className="main-view">{children}</main>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
export interface PublicDashboardFooterCfg {
|
export interface PublicDashboardFooterCfg {
|
||||||
@ -38,14 +38,10 @@ export let getPublicDashboardFooterConfig = (): PublicDashboardFooterCfg => ({
|
|||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
footer: css`
|
footer: css`
|
||||||
position: absolute;
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
bottom: 0;
|
padding: ${theme.spacing(0, 1, 0, 1)};
|
||||||
width: 100%;
|
|
||||||
background-color: ${colorManipulator.alpha(theme.colors.background.canvas, 0.7)};
|
|
||||||
text-align: right;
|
|
||||||
font-size: ${theme.typography.body.fontSize};
|
|
||||||
z-index: ${theme.zIndex.navbarFixed};
|
|
||||||
`,
|
`,
|
||||||
logoText: css`
|
logoText: css`
|
||||||
margin-right: ${theme.spacing(1)};
|
margin-right: ${theme.spacing(1)};
|
||||||
|
@ -320,20 +320,4 @@ describe('DashboardPage', () => {
|
|||||||
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
|
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dashboardPageScenario('When dashboard is public', (ctx) => {
|
|
||||||
ctx.setup(() => {
|
|
||||||
locationService.partial({ kiosk: false });
|
|
||||||
ctx.mount({
|
|
||||||
queryParams: {},
|
|
||||||
dashboard: getTestDashboard(),
|
|
||||||
});
|
|
||||||
ctx.rerender({ dashboard: ctx.dashboard, isPublic: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render page toolbar and submenu', () => {
|
|
||||||
expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(0);
|
|
||||||
expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,6 @@ import { DashboardPrompt } from '../components/DashboardPrompt/DashboardPrompt';
|
|||||||
import { DashboardSettings } from '../components/DashboardSettings';
|
import { DashboardSettings } from '../components/DashboardSettings';
|
||||||
import { PanelInspector } from '../components/Inspector/PanelInspector';
|
import { PanelInspector } from '../components/Inspector/PanelInspector';
|
||||||
import { PanelEditor } from '../components/PanelEditor/PanelEditor';
|
import { PanelEditor } from '../components/PanelEditor/PanelEditor';
|
||||||
import { PublicDashboardFooter } from '../components/PublicDashboardFooter/PublicDashboardsFooter';
|
|
||||||
import { SubMenu } from '../components/SubMenu/SubMenu';
|
import { SubMenu } from '../components/SubMenu/SubMenu';
|
||||||
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
||||||
import { liveTimer } from '../dashgrid/liveTimer';
|
import { liveTimer } from '../dashgrid/liveTimer';
|
||||||
@ -75,12 +74,7 @@ const mapDispatchToProps = {
|
|||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
type OwnProps = {
|
export type Props = Themeable2 &
|
||||||
isPublic?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Props = OwnProps &
|
|
||||||
Themeable2 &
|
|
||||||
GrafanaRouteComponentProps<DashboardPageRouteParams, DashboardPageRouteSearchParams> &
|
GrafanaRouteComponentProps<DashboardPageRouteParams, DashboardPageRouteSearchParams> &
|
||||||
ConnectedProps<typeof connector>;
|
ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
@ -129,7 +123,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initDashboard() {
|
initDashboard() {
|
||||||
const { dashboard, isPublic, match, queryParams } = this.props;
|
const { dashboard, match, queryParams } = this.props;
|
||||||
|
|
||||||
if (dashboard) {
|
if (dashboard) {
|
||||||
this.closeDashboard();
|
this.closeDashboard();
|
||||||
@ -142,7 +136,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
urlFolderUid: queryParams.folderUid,
|
urlFolderUid: queryParams.folderUid,
|
||||||
panelType: queryParams.panelType,
|
panelType: queryParams.panelType,
|
||||||
routeName: this.props.route.routeName,
|
routeName: this.props.route.routeName,
|
||||||
fixUrl: !isPublic,
|
fixUrl: true,
|
||||||
accessToken: match.params.accessToken,
|
accessToken: match.params.accessToken,
|
||||||
keybindingSrv: this.context.keybindings,
|
keybindingSrv: this.context.keybindings,
|
||||||
});
|
});
|
||||||
@ -341,9 +335,9 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, initError, queryParams, isPublic } = this.props;
|
const { dashboard, initError, queryParams } = this.props;
|
||||||
const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state;
|
const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state;
|
||||||
const kioskMode = !isPublic ? getKioskMode(this.props.queryParams) : KioskMode.Full;
|
const kioskMode = getKioskMode(this.props.queryParams);
|
||||||
|
|
||||||
if (!dashboard || !pageNav || !sectionNav) {
|
if (!dashboard || !pageNav || !sectionNav) {
|
||||||
return <DashboardLoading initPhase={this.props.initPhase} />;
|
return <DashboardLoading initPhase={this.props.initPhase} />;
|
||||||
@ -418,10 +412,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
sectionNav={sectionNav}
|
sectionNav={sectionNav}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{
|
|
||||||
// TODO: assess if there are other places where we may want a footer, which may reveal a better place to add this
|
|
||||||
isPublic && <PublicDashboardFooter />
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,207 @@
|
|||||||
|
import { render, RenderResult, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
import { useEffectOnce } from 'react-use';
|
||||||
|
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import { Dashboard, DashboardCursorSync } from '@grafana/schema/src';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
|
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||||
|
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
|
||||||
|
import * as appTypes from 'app/types';
|
||||||
|
|
||||||
|
import { SafeDynamicImport } from '../../../core/components/DynamicImports/SafeDynamicImport';
|
||||||
|
import { configureStore } from '../../../store/configureStore';
|
||||||
|
import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader';
|
||||||
|
import { DashboardModel } from '../state';
|
||||||
|
import { initDashboard } from '../state/initDashboard';
|
||||||
|
|
||||||
|
import PublicDashboardPage, { Props } from './PublicDashboardPage';
|
||||||
|
|
||||||
|
jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||||
|
const LazyLoader = ({ children, onLoad }: Pick<LazyLoaderProps, 'children' | 'onLoad'>) => {
|
||||||
|
useEffectOnce(() => {
|
||||||
|
onLoad?.();
|
||||||
|
});
|
||||||
|
return <>{typeof children === 'function' ? children({ isInView: true }) : children}</>;
|
||||||
|
};
|
||||||
|
return { LazyLoader };
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('react-virtualized-auto-sizer', () => {
|
||||||
|
// // // The size of the children need to be small enough to be outside the view.
|
||||||
|
// // // So it does not trigger the query to be run by the PanelQueryRunner.
|
||||||
|
return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('app/features/dashboard/state/initDashboard', () => ({
|
||||||
|
...jest.requireActual('app/features/dashboard/state/initDashboard'),
|
||||||
|
initDashboard: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('app/types', () => ({
|
||||||
|
...jest.requireActual('app/types'),
|
||||||
|
useDispatch: () => jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const renderWithProvider = ({
|
||||||
|
props,
|
||||||
|
initialState,
|
||||||
|
}: {
|
||||||
|
props: Props;
|
||||||
|
initialState?: Partial<appTypes.StoreState>;
|
||||||
|
}): RenderResult => {
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
const store = configureStore(initialState);
|
||||||
|
|
||||||
|
return render(
|
||||||
|
<GrafanaContext.Provider value={context}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router history={locationService.getHistory()}>
|
||||||
|
<PublicDashboardPage {...props} />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ScenarioContext {
|
||||||
|
mount: () => void;
|
||||||
|
rerender: ({
|
||||||
|
propOverrides,
|
||||||
|
newState,
|
||||||
|
}: {
|
||||||
|
propOverrides?: Partial<Props>;
|
||||||
|
newState?: Partial<appTypes.StoreState>;
|
||||||
|
}) => void;
|
||||||
|
setup: (fn: () => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel => {
|
||||||
|
const data: Dashboard = Object.assign(
|
||||||
|
{
|
||||||
|
title: 'My dashboard',
|
||||||
|
revision: 1,
|
||||||
|
editable: false,
|
||||||
|
graphTooltip: DashboardCursorSync.Off,
|
||||||
|
schemaVersion: 1,
|
||||||
|
style: 'dark',
|
||||||
|
panels: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'timeseries',
|
||||||
|
title: 'My panel title',
|
||||||
|
gridPos: { x: 0, y: 0, w: 1, h: 1 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
overrides
|
||||||
|
);
|
||||||
|
|
||||||
|
return new DashboardModel(data, metaOverrides);
|
||||||
|
};
|
||||||
|
|
||||||
|
function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioContext) => void) {
|
||||||
|
describe(description, () => {
|
||||||
|
let setupFn: () => void;
|
||||||
|
|
||||||
|
const ctx: ScenarioContext = {
|
||||||
|
setup: (fn) => {
|
||||||
|
setupFn = fn;
|
||||||
|
},
|
||||||
|
mount: () => {
|
||||||
|
const props: Props = {
|
||||||
|
...getRouteComponentProps({
|
||||||
|
match: { params: { accessToken: 'an-access-token' }, isExact: true, url: '', path: '' },
|
||||||
|
route: {
|
||||||
|
routeName: DashboardRoutes.Public,
|
||||||
|
path: '/public-dashboards/:accessToken',
|
||||||
|
component: SafeDynamicImport(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "PublicDashboardPage"*/ 'app/features/dashboard/containers/PublicDashboardPage'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = renderWithProvider({ props });
|
||||||
|
|
||||||
|
ctx.rerender = ({
|
||||||
|
propsOverride,
|
||||||
|
newState,
|
||||||
|
}: {
|
||||||
|
propsOverride?: Partial<Props>;
|
||||||
|
newState?: Partial<appTypes.StoreState>;
|
||||||
|
}) => {
|
||||||
|
Object.assign(props, propsOverride);
|
||||||
|
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
const store = configureStore(newState);
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<GrafanaContext.Provider value={context}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router history={locationService.getHistory()}>
|
||||||
|
<PublicDashboardPage {...props} />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rerender: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setupFn();
|
||||||
|
});
|
||||||
|
|
||||||
|
scenarioFn(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PublicDashboardPage', () => {
|
||||||
|
dashboardPageScenario('Given initial state', (ctx) => {
|
||||||
|
ctx.setup(() => {
|
||||||
|
ctx.mount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should call initDashboard on mount', () => {
|
||||||
|
expect(initDashboard).toBeCalledWith({
|
||||||
|
fixUrl: false,
|
||||||
|
accessToken: 'an-access-token',
|
||||||
|
routeName: 'public-dashboard',
|
||||||
|
keybindingSrv: expect.anything(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dashboardPageScenario('Given a simple dashboard', (ctx) => {
|
||||||
|
ctx.setup(() => {
|
||||||
|
ctx.mount();
|
||||||
|
ctx.rerender({
|
||||||
|
newState: {
|
||||||
|
dashboard: {
|
||||||
|
getModel: getTestDashboard,
|
||||||
|
initError: null,
|
||||||
|
initPhase: DashboardInitPhase.Completed,
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render panels', () => {
|
||||||
|
expect(screen.getByText('My panel title')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should update title', () => {
|
||||||
|
expect(document.title).toBe('My dashboard - Grafana');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,13 +1,112 @@
|
|||||||
import React from 'react';
|
import { css } from '@emotion/css';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { usePrevious } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaRouteComponentProps } from '../../../core/navigation/types';
|
import { GrafanaTheme2, PageLayoutType, TimeZone } from '@grafana/data';
|
||||||
|
import { PageToolbar, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
|
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||||
|
import { useSelector, useDispatch } from 'app/types';
|
||||||
|
|
||||||
import DashboardPage, { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './DashboardPage';
|
import { DashNavTimeControls } from '../components/DashNav/DashNavTimeControls';
|
||||||
|
import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed';
|
||||||
|
import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading';
|
||||||
|
import { PublicDashboardFooter } from '../components/PublicDashboardFooter/PublicDashboardsFooter';
|
||||||
|
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
||||||
|
import { getTimeSrv } from '../services/TimeSrv';
|
||||||
|
import { DashboardModel } from '../state';
|
||||||
|
import { initDashboard } from '../state/initDashboard';
|
||||||
|
|
||||||
export type Props = GrafanaRouteComponentProps<DashboardPageRouteParams, DashboardPageRouteSearchParams>;
|
interface PublicDashboardPageRouteParams {
|
||||||
|
accessToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const PublicDashboardPage = (props: Props) => {
|
interface PublicDashboardPageRouteSearchParams {
|
||||||
return <DashboardPage isPublic {...props} />;
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
refresh?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = GrafanaRouteComponentProps<PublicDashboardPageRouteParams, PublicDashboardPageRouteSearchParams>;
|
||||||
|
|
||||||
|
const Toolbar = ({ dashboard }: { dashboard: DashboardModel }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onChangeTimeZone = (timeZone: TimeZone) => {
|
||||||
|
dispatch(updateTimeZoneForSession(timeZone));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageToolbar title={dashboard.title} buttonOverflowAlignment="right">
|
||||||
|
{!dashboard.timepicker.hidden && (
|
||||||
|
<DashNavTimeControls dashboard={dashboard} onChangeTimeZone={onChangeTimeZone} />
|
||||||
|
)}
|
||||||
|
</PageToolbar>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PublicDashboardPage = (props: Props) => {
|
||||||
|
const { match, route, location } = props;
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const context = useGrafana();
|
||||||
|
const prevProps = usePrevious(props);
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const dashboardState = useSelector((store) => store.dashboard);
|
||||||
|
const dashboard = dashboardState.getModel();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(
|
||||||
|
initDashboard({
|
||||||
|
routeName: route.routeName,
|
||||||
|
fixUrl: false,
|
||||||
|
accessToken: match.params.accessToken,
|
||||||
|
keybindingSrv: context.keybindings,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevProps?.location.search !== location.search) {
|
||||||
|
const prevUrlParams = prevProps?.queryParams;
|
||||||
|
const urlParams = props.queryParams;
|
||||||
|
|
||||||
|
if (urlParams?.from !== prevUrlParams?.from || urlParams?.to !== prevUrlParams?.to) {
|
||||||
|
getTimeSrv().updateTimeRangeFromUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prevUrlParams?.refresh && urlParams?.refresh) {
|
||||||
|
getTimeSrv().setAutoRefresh(urlParams.refresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [prevProps, location.search, props.queryParams]);
|
||||||
|
|
||||||
|
if (!dashboard) {
|
||||||
|
return <DashboardLoading initPhase={dashboardState.initPhase} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page
|
||||||
|
pageNav={{ text: dashboard.title }}
|
||||||
|
layout={PageLayoutType.Custom}
|
||||||
|
toolbar={<Toolbar dashboard={dashboard} />}
|
||||||
|
>
|
||||||
|
{dashboardState.initError && <DashboardFailed />}
|
||||||
|
<div className={styles.gridContainer}>
|
||||||
|
<DashboardGrid dashboard={dashboard} isEditable={false} viewPanel={null} editPanel={null} />
|
||||||
|
</div>
|
||||||
|
<PublicDashboardFooter />
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
gridContainer: css({
|
||||||
|
padding: theme.spacing(0, 2, 2, 2),
|
||||||
|
overflow: 'auto',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export default PublicDashboardPage;
|
export default PublicDashboardPage;
|
||||||
|
@ -21,6 +21,7 @@ export const getPublicDashboardRoutes = (): RouteDescriptor[] => {
|
|||||||
path: '/public-dashboards/:accessToken',
|
path: '/public-dashboards/:accessToken',
|
||||||
pageClass: 'page-dashboard',
|
pageClass: 'page-dashboard',
|
||||||
routeName: DashboardRoutes.Public,
|
routeName: DashboardRoutes.Public,
|
||||||
|
chromeless: true,
|
||||||
component: SafeDynamicImport(
|
component: SafeDynamicImport(
|
||||||
() =>
|
() =>
|
||||||
import(
|
import(
|
||||||
|
@ -57,8 +57,6 @@
|
|||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
nav.page-toolbar {
|
nav.page-toolbar {
|
||||||
padding-left: 53px;
|
|
||||||
|
|
||||||
&--fullscreen {
|
&--fullscreen {
|
||||||
padding-left: $space-md;
|
padding-left: $space-md;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user