mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SingleTopNav: Revert to using AppChromeUpdate
so banners are correct (#94540)
* revert to using AppChromeUpdate * fix dashboard settings in old arch * remove empty interface * fix AlertRuleForm
This commit is contained in:
parent
a5d72e264d
commit
4d08f44667
@ -18,6 +18,7 @@ import { MegaMenu, MENU_WIDTH } from './MegaMenu/MegaMenu';
|
||||
import { NavToolbar } from './NavToolbar/NavToolbar';
|
||||
import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious';
|
||||
import { SingleTopBar } from './TopBar/SingleTopBar';
|
||||
import { SingleTopBarActions } from './TopBar/SingleTopBarActions';
|
||||
import { TopSearchBar } from './TopBar/TopSearchBar';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
||||
|
||||
@ -28,7 +29,7 @@ export function AppChrome({ children }: Props) {
|
||||
const state = chrome.useState();
|
||||
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getStyles, searchBarHidden);
|
||||
const styles = useStyles2(getStyles, searchBarHidden, Boolean(state.actions));
|
||||
|
||||
const dockedMenuBreakpoint = theme.breakpoints.values.xl;
|
||||
const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true);
|
||||
@ -99,12 +100,15 @@ export function AppChrome({ children }: Props) {
|
||||
)}
|
||||
<header className={cx(styles.topNav, isSingleTopNav && menuDockedAndOpen && styles.topNavMenuDocked)}>
|
||||
{isSingleTopNav ? (
|
||||
<SingleTopBar
|
||||
sectionNav={state.sectionNav.node}
|
||||
pageNav={state.pageNav}
|
||||
onToggleMegaMenu={handleMegaMenu}
|
||||
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||
/>
|
||||
<>
|
||||
<SingleTopBar
|
||||
sectionNav={state.sectionNav.node}
|
||||
pageNav={state.pageNav}
|
||||
onToggleMegaMenu={handleMegaMenu}
|
||||
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||
/>
|
||||
{state.actions && <SingleTopBarActions>{state.actions}</SingleTopBarActions>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!searchBarHidden && <TopSearchBar />}
|
||||
@ -156,13 +160,13 @@ export function AppChrome({ children }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
|
||||
const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean, hasActions: boolean) => {
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
return {
|
||||
content: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingTop: isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2,
|
||||
paddingTop: !isSingleTopNav || hasActions ? TOP_BAR_LEVEL_HEIGHT * 2 : TOP_BAR_LEVEL_HEIGHT,
|
||||
flexGrow: 1,
|
||||
height: 'auto',
|
||||
}),
|
||||
|
@ -7,7 +7,7 @@ export interface AppChromeUpdateProps {
|
||||
actions?: React.ReactNode;
|
||||
}
|
||||
/**
|
||||
* @deprecated This component is deprecated and will be removed in a future release.
|
||||
* This is the way core pages add actions to the second chrome toolbar
|
||||
*/
|
||||
export const AppChromeUpdate = React.memo<AppChromeUpdateProps>(({ actions }: AppChromeUpdateProps) => {
|
||||
const { chrome } = useGrafana();
|
||||
|
@ -13,6 +13,8 @@ import { setBookmark } from 'app/core/reducers/navBarTree';
|
||||
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from '../types';
|
||||
|
||||
import { MegaMenuHeader } from './MegaMenuHeader';
|
||||
import { MegaMenuItem } from './MegaMenuItem';
|
||||
import { usePinnedItems } from './hooks';
|
||||
@ -166,41 +168,44 @@ export const MegaMenu = memo(
|
||||
|
||||
MegaMenu.displayName = 'MegaMenu';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
content: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
position: 'relative',
|
||||
}),
|
||||
mobileHeader: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(1, 1, 1, 2),
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
return {
|
||||
content: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isSingleTopNav ? `calc(100% - ${TOP_BAR_LEVEL_HEIGHT}px)` : '100%',
|
||||
minHeight: 0,
|
||||
position: 'relative',
|
||||
}),
|
||||
mobileHeader: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(1, 1, 1, 2),
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
|
||||
[theme.breakpoints.up('md')]: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
itemList: css({
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
listStyleType: 'none',
|
||||
padding: theme.spacing(1, 1, 2, 1),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: MENU_WIDTH,
|
||||
},
|
||||
}),
|
||||
dockMenuButton: css({
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
itemList: css({
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
listStyleType: 'none',
|
||||
padding: theme.spacing(1, 1, 2, 1),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: MENU_WIDTH,
|
||||
},
|
||||
}),
|
||||
dockMenuButton: css({
|
||||
display: 'none',
|
||||
position: 'relative',
|
||||
top: theme.spacing(1),
|
||||
position: 'relative',
|
||||
top: theme.spacing(1),
|
||||
|
||||
[theme.breakpoints.up('xl')]: {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
}),
|
||||
});
|
||||
[theme.breakpoints.up('xl')]: {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Components } from '@grafana/e2e-selectors';
|
||||
import { Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from '../types';
|
||||
|
||||
export function SingleTopBarActions({ children }: PropsWithChildren) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div data-testid={Components.NavToolbar.container} className={styles.actionsBar}>
|
||||
<Stack alignItems="center" justifyContent="flex-end" flex={1} wrap="nowrap" minWidth={0}>
|
||||
{children}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
actionsBar: css({
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.background.primary,
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
display: 'flex',
|
||||
height: TOP_BAR_LEVEL_HEIGHT,
|
||||
padding: theme.spacing(0, 1, 0, 2),
|
||||
}),
|
||||
};
|
||||
};
|
@ -1,58 +1,19 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types';
|
||||
import NativeScrollbar from '../NativeScrollbar';
|
||||
|
||||
import { PageContents } from './PageContents';
|
||||
import { PageHeader } from './PageHeader';
|
||||
import { PageTabs } from './PageTabs';
|
||||
import { PageToolbarActions } from './PageToolbarActions';
|
||||
import { PageType } from './types';
|
||||
import { usePageNav } from './usePageNav';
|
||||
import { usePageTitle } from './usePageTitle';
|
||||
|
||||
export interface PageContextType {
|
||||
setToolbar: Dispatch<SetStateAction<ReactNode>>;
|
||||
}
|
||||
|
||||
export const PageContext = createContext<PageContextType | undefined>(undefined);
|
||||
|
||||
function usePageContext(): PageContextType {
|
||||
const context = useContext(PageContext);
|
||||
if (!context) {
|
||||
throw new Error('No PageContext found');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to dynamically set the toolbar of a Page from a child component.
|
||||
* Prefer setting the toolbar directly as a prop to Page.
|
||||
* @param toolbar a ReactNode that will be rendered in a second toolbar
|
||||
*/
|
||||
export function usePageToolbar(toolbar?: ReactNode) {
|
||||
const { setToolbar } = usePageContext();
|
||||
useEffect(() => {
|
||||
setToolbar(toolbar);
|
||||
return () => setToolbar(undefined);
|
||||
}, [setToolbar, toolbar]);
|
||||
}
|
||||
|
||||
export const Page: PageType = ({
|
||||
navId,
|
||||
navModel: oldNavProp,
|
||||
@ -63,15 +24,12 @@ export const Page: PageType = ({
|
||||
subTitle,
|
||||
children,
|
||||
className,
|
||||
toolbar: toolbarProp,
|
||||
info,
|
||||
layout = PageLayoutType.Standard,
|
||||
onSetScrollRef,
|
||||
...otherProps
|
||||
}) => {
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
const [toolbar, setToolbar] = useState(toolbarProp);
|
||||
const styles = useStyles2(getStyles, Boolean(isSingleTopNav && toolbar));
|
||||
const styles = useStyles2(getStyles);
|
||||
const navModel = usePageNav(navId, oldNavProp);
|
||||
const { chrome } = useGrafana();
|
||||
|
||||
@ -92,58 +50,54 @@ export const Page: PageType = ({
|
||||
}, [navModel, pageNav, chrome, layout]);
|
||||
|
||||
return (
|
||||
<PageContext.Provider value={{ setToolbar }}>
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps}>
|
||||
{isSingleTopNav && toolbar && <PageToolbarActions>{toolbar}</PageToolbarActions>}
|
||||
{layout === PageLayoutType.Standard && (
|
||||
<NativeScrollbar
|
||||
// This id is used by the image renderer to scroll through the dashboard
|
||||
divId="page-scrollbar"
|
||||
onSetScrollRef={onSetScrollRef}
|
||||
>
|
||||
<div className={styles.pageInner}>
|
||||
{pageHeaderNav && (
|
||||
<PageHeader
|
||||
actions={actions}
|
||||
onEditTitle={onEditTitle}
|
||||
navItem={pageHeaderNav}
|
||||
renderTitle={renderTitle}
|
||||
info={info}
|
||||
subTitle={subTitle}
|
||||
/>
|
||||
)}
|
||||
{pageNav && pageNav.children && <PageTabs navItem={pageNav} />}
|
||||
<div className={styles.pageContent}>{children}</div>
|
||||
</div>
|
||||
</NativeScrollbar>
|
||||
)}
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps}>
|
||||
{layout === PageLayoutType.Standard && (
|
||||
<NativeScrollbar
|
||||
// This id is used by the image renderer to scroll through the dashboard
|
||||
divId="page-scrollbar"
|
||||
onSetScrollRef={onSetScrollRef}
|
||||
>
|
||||
<div className={styles.pageInner}>
|
||||
{pageHeaderNav && (
|
||||
<PageHeader
|
||||
actions={actions}
|
||||
onEditTitle={onEditTitle}
|
||||
navItem={pageHeaderNav}
|
||||
renderTitle={renderTitle}
|
||||
info={info}
|
||||
subTitle={subTitle}
|
||||
/>
|
||||
)}
|
||||
{pageNav && pageNav.children && <PageTabs navItem={pageNav} />}
|
||||
<div className={styles.pageContent}>{children}</div>
|
||||
</div>
|
||||
</NativeScrollbar>
|
||||
)}
|
||||
|
||||
{layout === PageLayoutType.Canvas && (
|
||||
<NativeScrollbar
|
||||
// This id is used by the image renderer to scroll through the dashboard
|
||||
divId="page-scrollbar"
|
||||
onSetScrollRef={onSetScrollRef}
|
||||
>
|
||||
<div className={styles.canvasContent}>{children}</div>
|
||||
</NativeScrollbar>
|
||||
)}
|
||||
{layout === PageLayoutType.Canvas && (
|
||||
<NativeScrollbar
|
||||
// This id is used by the image renderer to scroll through the dashboard
|
||||
divId="page-scrollbar"
|
||||
onSetScrollRef={onSetScrollRef}
|
||||
>
|
||||
<div className={styles.canvasContent}>{children}</div>
|
||||
</NativeScrollbar>
|
||||
)}
|
||||
|
||||
{layout === PageLayoutType.Custom && children}
|
||||
</div>
|
||||
</PageContext.Provider>
|
||||
{layout === PageLayoutType.Custom && children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Page.Contents = PageContents;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, hasToolbar: boolean) => {
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
wrapper: css({
|
||||
label: 'page-wrapper',
|
||||
display: 'flex',
|
||||
flex: '1 1 0',
|
||||
flexDirection: 'column',
|
||||
marginTop: hasToolbar ? TOP_BAR_LEVEL_HEIGHT : 0,
|
||||
position: 'relative',
|
||||
}),
|
||||
pageContent: css({
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Components } from '@grafana/e2e-selectors';
|
||||
import { useChromeHeaderHeight } from '@grafana/runtime';
|
||||
import { Stack, useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
import { MENU_WIDTH } from '../AppChrome/MegaMenu/MegaMenu';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types';
|
||||
|
||||
export interface Props {}
|
||||
|
||||
export function PageToolbarActions({ children }: PropsWithChildren<Props>) {
|
||||
const chromeHeaderHeight = useChromeHeaderHeight();
|
||||
const { chrome } = useGrafana();
|
||||
const state = chrome.useState();
|
||||
const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen;
|
||||
const styles = useStyles2(getStyles, chromeHeaderHeight ?? 0, menuDockedAndOpen);
|
||||
|
||||
return (
|
||||
<div data-testid={Components.NavToolbar.container} className={styles.pageToolbar}>
|
||||
<Stack alignItems="center" justifyContent="flex-end" flex={1} wrap="nowrap" minWidth={0}>
|
||||
{children}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, chromeHeaderHeight: number, menuDockedAndOpen: boolean) => {
|
||||
return {
|
||||
pageToolbar: css({
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.background.primary,
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
display: 'flex',
|
||||
height: TOP_BAR_LEVEL_HEIGHT,
|
||||
left: menuDockedAndOpen ? MENU_WIDTH : 0,
|
||||
padding: theme.spacing(0, 1, 0, 2),
|
||||
position: 'fixed',
|
||||
top: chromeHeaderHeight,
|
||||
right: 0,
|
||||
zIndex: theme.zIndex.navbarFixed,
|
||||
}),
|
||||
};
|
||||
};
|
@ -25,8 +25,6 @@ export interface PageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
layout?: PageLayoutType;
|
||||
/** Can be used to get the scroll container element to access scroll position */
|
||||
onSetScrollRef?: (ref: ScrollRefElement) => void;
|
||||
/** Set a page-level toolbar */
|
||||
toolbar?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface PageInfoItem {
|
||||
|
@ -4,6 +4,7 @@ import { GrafanaConfig } from '@grafana/data';
|
||||
import { LocationService, locationService, BackendSrv, config } from '@grafana/runtime';
|
||||
|
||||
import { AppChromeService } from '../components/AppChrome/AppChromeService';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from '../components/AppChrome/types';
|
||||
import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker';
|
||||
import { KeybindingSrv } from '../services/keybindingSrv';
|
||||
|
||||
@ -42,17 +43,25 @@ export function useReturnToPreviousInternal() {
|
||||
);
|
||||
}
|
||||
|
||||
const SINGLE_HEADER_BAR_HEIGHT = 40;
|
||||
|
||||
export function useChromeHeaderHeight() {
|
||||
const { chrome } = useGrafana();
|
||||
const { kioskMode, searchBarHidden, chromeless } = chrome.useState();
|
||||
const { actions, kioskMode, searchBarHidden, chromeless } = chrome.useState();
|
||||
|
||||
if (kioskMode || chromeless) {
|
||||
return 0;
|
||||
} else if (searchBarHidden || config.featureToggles.singleTopNav) {
|
||||
return SINGLE_HEADER_BAR_HEIGHT;
|
||||
if (config.featureToggles.singleTopNav) {
|
||||
if (kioskMode || chromeless) {
|
||||
return 0;
|
||||
} else if (actions) {
|
||||
return TOP_BAR_LEVEL_HEIGHT * 2;
|
||||
} else {
|
||||
return TOP_BAR_LEVEL_HEIGHT;
|
||||
}
|
||||
} else {
|
||||
return SINGLE_HEADER_BAR_HEIGHT * 2;
|
||||
if (kioskMode || chromeless) {
|
||||
return 0;
|
||||
} else if (searchBarHidden) {
|
||||
return TOP_BAR_LEVEL_HEIGHT;
|
||||
} else {
|
||||
return TOP_BAR_LEVEL_HEIGHT * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors/src';
|
||||
import { setDataSourceSrv } from '@grafana/runtime';
|
||||
import { PageContext } from 'app/core/components/Page/Page';
|
||||
import { DashboardSearchItem, DashboardSearchItemType } from 'app/features/search/types';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
|
||||
@ -73,9 +72,7 @@ function Wrapper({ children }: React.PropsWithChildren<{}>) {
|
||||
const formApi = useForm<RuleFormValues>({ defaultValues: getDefaultFormValues() });
|
||||
return (
|
||||
<Providers>
|
||||
<PageContext.Provider value={{ setToolbar: jest.fn() }}>
|
||||
<FormProvider {...formApi}>{children}</FormProvider>
|
||||
</PageContext.Provider>
|
||||
<FormProvider {...formApi}>{children}</FormProvider>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { addMinutes, subDays, subHours } from 'date-fns';
|
||||
import { Location } from 'history';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useToggle } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config as runtimeConfig, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { isFetchError, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@ -21,7 +21,6 @@ import {
|
||||
InlineField,
|
||||
Box,
|
||||
} from '@grafana/ui';
|
||||
import { usePageToolbar } from 'app/core/components/Page/Page';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||
import { ActiveTab as ContactPointsActiveTabs } from 'app/features/alerting/unified/components/contact-points/ContactPoints';
|
||||
@ -158,33 +157,28 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
||||
}
|
||||
};
|
||||
|
||||
const actionButtons = useMemo(
|
||||
() => (
|
||||
<Stack>
|
||||
<Button onClick={() => formRef.current?.requestSubmit()} variant="primary" size="sm" disabled={isSubmitting}>
|
||||
Save
|
||||
</Button>
|
||||
<LinkButton
|
||||
disabled={isSubmitting}
|
||||
href={makeAMLink('alerting/notifications', alertmanager, {
|
||||
tab: ContactPointsActiveTabs.NotificationTemplates,
|
||||
})}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
Cancel
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
),
|
||||
[alertmanager, isSubmitting]
|
||||
const actionButtons = (
|
||||
<Stack>
|
||||
<Button onClick={() => formRef.current?.requestSubmit()} variant="primary" size="sm" disabled={isSubmitting}>
|
||||
Save
|
||||
</Button>
|
||||
<LinkButton
|
||||
disabled={isSubmitting}
|
||||
href={makeAMLink('alerting/notifications', alertmanager, {
|
||||
tab: ContactPointsActiveTabs.NotificationTemplates,
|
||||
})}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
Cancel
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
usePageToolbar(actionButtons);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormProvider {...formApi}>
|
||||
{!runtimeConfig.featureToggles.singleTopNav && <AppChromeUpdate actions={actionButtons} />}
|
||||
<AppChromeUpdate actions={actionButtons} />
|
||||
<form onSubmit={handleSubmit(submit)} ref={formRef} className={styles.form} aria-label="Template form">
|
||||
{/* error message */}
|
||||
{error && (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, SubmitErrorHandler, useForm, UseFormWatch } from 'react-hook-form';
|
||||
import { useParams } from 'react-router-dom-v5-compat';
|
||||
|
||||
@ -7,7 +7,6 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import { Button, ConfirmModal, CustomScrollbar, Spinner, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { usePageToolbar } from 'app/core/components/Page/Page';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule';
|
||||
@ -136,66 +135,52 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
};
|
||||
|
||||
// @todo why is error not propagated to form?
|
||||
const submit = useCallback(
|
||||
async (values: RuleFormValues, exitOnSave: boolean) => {
|
||||
if (conditionErrorMsg !== '') {
|
||||
notifyApp.error(conditionErrorMsg);
|
||||
return;
|
||||
}
|
||||
const submit = async (values: RuleFormValues, exitOnSave: boolean) => {
|
||||
if (conditionErrorMsg !== '') {
|
||||
notifyApp.error(conditionErrorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type });
|
||||
trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type });
|
||||
|
||||
const ruleDefinition = grafanaTypeRule
|
||||
? formValuesToRulerGrafanaRuleDTO(values)
|
||||
: formValuesToRulerRuleDTO(values);
|
||||
const ruleDefinition = grafanaTypeRule ? formValuesToRulerGrafanaRuleDTO(values) : formValuesToRulerRuleDTO(values);
|
||||
|
||||
const ruleGroupIdentifier = existing
|
||||
? getRuleGroupLocationFromRuleWithLocation(existing)
|
||||
: getRuleGroupLocationFromFormValues(values);
|
||||
const ruleGroupIdentifier = existing
|
||||
? getRuleGroupLocationFromRuleWithLocation(existing)
|
||||
: getRuleGroupLocationFromFormValues(values);
|
||||
|
||||
// @TODO move this to a hook too to make sure the logic here is tested for regressions?
|
||||
if (!existing) {
|
||||
// when creating a new rule, we save the manual routing setting , and editorSettings.simplifiedQueryEditor to the local storage
|
||||
storeInLocalStorageValues(values);
|
||||
await addRuleToRuleGroup.execute(ruleGroupIdentifier, ruleDefinition, evaluateEvery);
|
||||
} else {
|
||||
const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule);
|
||||
const targetRuleGroupIdentifier = getRuleGroupLocationFromFormValues(values);
|
||||
await updateRuleInRuleGroup.execute(
|
||||
ruleGroupIdentifier,
|
||||
ruleIdentifier,
|
||||
ruleDefinition,
|
||||
targetRuleGroupIdentifier,
|
||||
evaluateEvery
|
||||
);
|
||||
}
|
||||
// @TODO move this to a hook too to make sure the logic here is tested for regressions?
|
||||
if (!existing) {
|
||||
// when creating a new rule, we save the manual routing setting , and editorSettings.simplifiedQueryEditor to the local storage
|
||||
storeInLocalStorageValues(values);
|
||||
await addRuleToRuleGroup.execute(ruleGroupIdentifier, ruleDefinition, evaluateEvery);
|
||||
} else {
|
||||
const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule);
|
||||
const targetRuleGroupIdentifier = getRuleGroupLocationFromFormValues(values);
|
||||
await updateRuleInRuleGroup.execute(
|
||||
ruleGroupIdentifier,
|
||||
ruleIdentifier,
|
||||
ruleDefinition,
|
||||
targetRuleGroupIdentifier,
|
||||
evaluateEvery
|
||||
);
|
||||
}
|
||||
|
||||
const { dataSourceName, namespaceName, groupName } = ruleGroupIdentifier;
|
||||
if (exitOnSave) {
|
||||
const returnTo = queryParams.get('returnTo') || getReturnToUrl(ruleGroupIdentifier, ruleDefinition);
|
||||
const { dataSourceName, namespaceName, groupName } = ruleGroupIdentifier;
|
||||
if (exitOnSave) {
|
||||
const returnTo = queryParams.get('returnTo') || getReturnToUrl(ruleGroupIdentifier, ruleDefinition);
|
||||
|
||||
locationService.push(returnTo);
|
||||
return;
|
||||
}
|
||||
locationService.push(returnTo);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cloud Ruler rules identifier changes on update due to containing rule name and hash components
|
||||
// After successful update we need to update the URL to avoid displaying 404 errors
|
||||
if (isCloudRulerRule(ruleDefinition)) {
|
||||
const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition);
|
||||
locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`);
|
||||
}
|
||||
},
|
||||
[
|
||||
addRuleToRuleGroup,
|
||||
conditionErrorMsg,
|
||||
evaluateEvery,
|
||||
existing,
|
||||
grafanaTypeRule,
|
||||
notifyApp,
|
||||
queryParams,
|
||||
updateRuleInRuleGroup,
|
||||
]
|
||||
);
|
||||
// Cloud Ruler rules identifier changes on update due to containing rule name and hash components
|
||||
// After successful update we need to update the URL to avoid displaying 404 errors
|
||||
if (isCloudRulerRule(ruleDefinition)) {
|
||||
const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition);
|
||||
locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRule = async () => {
|
||||
if (existing) {
|
||||
@ -208,80 +193,73 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onInvalid: SubmitErrorHandler<RuleFormValues> = useCallback(
|
||||
(errors): void => {
|
||||
trackAlertRuleFormError({
|
||||
grafana_version: config.buildInfo.version,
|
||||
org_id: contextSrv.user.orgId,
|
||||
user_id: contextSrv.user.id,
|
||||
error: Object.keys(errors).toString(),
|
||||
formAction: existing ? 'update' : 'create',
|
||||
});
|
||||
notifyApp.error('There are errors in the form. Please correct them and try again!');
|
||||
},
|
||||
[existing, notifyApp]
|
||||
);
|
||||
const onInvalid: SubmitErrorHandler<RuleFormValues> = (errors): void => {
|
||||
trackAlertRuleFormError({
|
||||
grafana_version: config.buildInfo.version,
|
||||
org_id: contextSrv.user.orgId,
|
||||
user_id: contextSrv.user.id,
|
||||
error: Object.keys(errors).toString(),
|
||||
formAction: existing ? 'update' : 'create',
|
||||
});
|
||||
notifyApp.error('There are errors in the form. Please correct them and try again!');
|
||||
};
|
||||
|
||||
const cancelRuleCreation = useCallback(() => {
|
||||
const cancelRuleCreation = () => {
|
||||
logInfo(LogMessages.cancelSavingAlertRule);
|
||||
trackAlertRuleFormCancelled({ formAction: existing ? 'update' : 'create' });
|
||||
locationService.getHistory().goBack();
|
||||
}, [existing]);
|
||||
};
|
||||
|
||||
const evaluateEveryInForm = watch('evaluateEvery');
|
||||
useEffect(() => setEvaluateEvery(evaluateEveryInForm), [evaluateEveryInForm]);
|
||||
|
||||
const actionButtons = useMemo(
|
||||
() => (
|
||||
<Stack justifyContent="flex-end" alignItems="center">
|
||||
{existing && (
|
||||
<Button
|
||||
data-testid="save-rule"
|
||||
variant="primary"
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleSubmit((values) => submit(values, false), onInvalid)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Spinner className={styles.buttonSpinner} inline={true} />}
|
||||
Save rule
|
||||
</Button>
|
||||
)}
|
||||
const actionButtons = (
|
||||
<Stack justifyContent="flex-end" alignItems="center">
|
||||
{existing && (
|
||||
<Button
|
||||
data-testid="save-rule-and-exit"
|
||||
data-testid="save-rule"
|
||||
variant="primary"
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleSubmit((values) => submit(values, true), onInvalid)}
|
||||
onClick={handleSubmit((values) => submit(values, false), onInvalid)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Spinner className={styles.buttonSpinner} inline={true} />}
|
||||
Save rule and exit
|
||||
Save rule
|
||||
</Button>
|
||||
<Button variant="secondary" disabled={isSubmitting} type="button" onClick={cancelRuleCreation} size="sm">
|
||||
Cancel
|
||||
)}
|
||||
<Button
|
||||
data-testid="save-rule-and-exit"
|
||||
variant="primary"
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleSubmit((values) => submit(values, true), onInvalid)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Spinner className={styles.buttonSpinner} inline={true} />}
|
||||
Save rule and exit
|
||||
</Button>
|
||||
<Button variant="secondary" disabled={isSubmitting} type="button" onClick={cancelRuleCreation} size="sm">
|
||||
Cancel
|
||||
</Button>
|
||||
{existing ? (
|
||||
<Button fill="outline" variant="destructive" type="button" onClick={() => setShowDeleteModal(true)} size="sm">
|
||||
Delete
|
||||
</Button>
|
||||
{existing ? (
|
||||
<Button fill="outline" variant="destructive" type="button" onClick={() => setShowDeleteModal(true)} size="sm">
|
||||
Delete
|
||||
</Button>
|
||||
) : null}
|
||||
{existing && isCortexLokiOrRecordingRule(watch) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => setShowEditYaml(true)}
|
||||
disabled={isSubmitting}
|
||||
size="sm"
|
||||
>
|
||||
Edit YAML
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
),
|
||||
[cancelRuleCreation, existing, handleSubmit, isSubmitting, onInvalid, styles.buttonSpinner, submit, watch]
|
||||
) : null}
|
||||
{existing && isCortexLokiOrRecordingRule(watch) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => setShowEditYaml(true)}
|
||||
disabled={isSubmitting}
|
||||
size="sm"
|
||||
>
|
||||
Edit YAML
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
usePageToolbar(actionButtons);
|
||||
|
||||
const isPaused = existing && isGrafanaRulerRule(existing.rule) && isGrafanaRulerRulePaused(existing.rule);
|
||||
if (!type) {
|
||||
@ -289,7 +267,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
}
|
||||
return (
|
||||
<FormProvider {...formAPI}>
|
||||
{!config.featureToggles.singleTopNav && <AppChromeUpdate actions={actionButtons} />}
|
||||
<AppChromeUpdate actions={actionButtons} />
|
||||
<form onSubmit={(e) => e.preventDefault()} className={styles.form}>
|
||||
<div className={styles.contentOuter}>
|
||||
{isPaused && <InfoPausedRule />}
|
||||
|
@ -2,9 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, CustomScrollbar, LinkButton, LoadingPlaceholder, Stack } from '@grafana/ui';
|
||||
import { usePageToolbar } from 'app/core/components/Page/Page';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
|
||||
@ -52,47 +50,39 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor
|
||||
const [conditionErrorMsg, setConditionErrorMsg] = useState('');
|
||||
const [evaluateEvery, setEvaluateEvery] = useState(ruleForm?.evaluateEvery ?? DEFAULT_GROUP_EVALUATION_INTERVAL);
|
||||
|
||||
const onInvalid = useCallback((): void => {
|
||||
const onInvalid = (): void => {
|
||||
notifyApp.error('There are errors in the form. Please correct them and try again!');
|
||||
}, [notifyApp]);
|
||||
};
|
||||
|
||||
const checkAlertCondition = (msg = '') => {
|
||||
setConditionErrorMsg(msg);
|
||||
};
|
||||
|
||||
const submit = useCallback(
|
||||
(exportData: RuleFormValues | undefined) => {
|
||||
if (conditionErrorMsg !== '') {
|
||||
notifyApp.error(conditionErrorMsg);
|
||||
return;
|
||||
}
|
||||
setExportData(exportData);
|
||||
},
|
||||
[conditionErrorMsg, notifyApp]
|
||||
);
|
||||
const submit = (exportData: RuleFormValues | undefined) => {
|
||||
if (conditionErrorMsg !== '') {
|
||||
notifyApp.error(conditionErrorMsg);
|
||||
return;
|
||||
}
|
||||
setExportData(exportData);
|
||||
};
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setExportData(undefined);
|
||||
}, [setExportData]);
|
||||
|
||||
const actionButtons = useMemo(
|
||||
() => [
|
||||
<LinkButton href={returnTo} key="cancel" size="sm" variant="secondary" onClick={() => submit(undefined)}>
|
||||
Cancel
|
||||
</LinkButton>,
|
||||
<Button key="export-rule" size="sm" onClick={formAPI.handleSubmit((formValues) => submit(formValues), onInvalid)}>
|
||||
Export
|
||||
</Button>,
|
||||
],
|
||||
[formAPI, onInvalid, returnTo, submit]
|
||||
);
|
||||
|
||||
usePageToolbar(actionButtons);
|
||||
const actionButtons = [
|
||||
<LinkButton href={returnTo} key="cancel" size="sm" variant="secondary" onClick={() => submit(undefined)}>
|
||||
Cancel
|
||||
</LinkButton>,
|
||||
<Button key="export-rule" size="sm" onClick={formAPI.handleSubmit((formValues) => submit(formValues), onInvalid)}>
|
||||
Export
|
||||
</Button>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormProvider {...formAPI}>
|
||||
{!config.featureToggles.singleTopNav && <AppChromeUpdate actions={actionButtons} />}
|
||||
<AppChromeUpdate actions={actionButtons} />
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<div>
|
||||
<CustomScrollbar autoHeightMin="100%" hideHorizontalTrack={true}>
|
||||
|
@ -3,10 +3,9 @@ import { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { config, useChromeHeaderHeight } from '@grafana/runtime';
|
||||
import { useChromeHeaderHeight } from '@grafana/runtime';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from 'app/core/components/AppChrome/types';
|
||||
import NativeScrollbar from 'app/core/components/NativeScrollbar';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
|
||||
@ -15,7 +14,7 @@ import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from './NavToolbarActions';
|
||||
import { NavToolbarActions } from './NavToolbarActions';
|
||||
import { PanelSearchLayout } from './PanelSearchLayout';
|
||||
import { DashboardAngularDeprecationBanner } from './angular/DashboardAngularDeprecationBanner';
|
||||
|
||||
@ -31,7 +30,6 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
||||
const navModel = getNavModel(navIndex, 'dashboards/browse');
|
||||
const hasControls = controls?.hasControls();
|
||||
const isSettingsOpen = editview !== undefined;
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
// Remember scroll pos when going into view panel, edit panel or settings
|
||||
useMemo(() => {
|
||||
@ -81,17 +79,12 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Custom}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={model} /> : undefined}
|
||||
>
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||
{editPanel && <editPanel.Component model={editPanel} />}
|
||||
{!editPanel && (
|
||||
<NativeScrollbar divId="page-scrollbar" onSetScrollRef={model.onSetScrollRef}>
|
||||
<div className={cx(styles.pageContainer, hasControls && styles.pageContainerWithControls)}>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={model} />}
|
||||
<NavToolbarActions dashboard={model} />
|
||||
{controls && (
|
||||
<div className={styles.controlsWrapper}>
|
||||
<controls.Component model={controls} />
|
||||
@ -140,7 +133,7 @@ function getStyles(theme: GrafanaTheme2, headerHeight: number) {
|
||||
position: 'sticky',
|
||||
zIndex: theme.zIndex.activePanel,
|
||||
background: theme.colors.background.canvas,
|
||||
top: config.featureToggles.singleTopNav ? headerHeight + TOP_BAR_LEVEL_HEIGHT : headerHeight,
|
||||
top: headerHeight,
|
||||
},
|
||||
}),
|
||||
canvasContent: css({
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { AnnotationQuery, getDataSourceRef, NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, VizPanel, dataLayers } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
@ -133,7 +133,6 @@ function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditV
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const { editIndex } = model.useState();
|
||||
const panels = dashboardSceneGraph.getVizPanels(dashboard);
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
const annotations: AnnotationQuery[] = dataLayersToAnnotations(annotationLayers);
|
||||
|
||||
@ -154,13 +153,8 @@ function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditV
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<AnnotationSettingsList
|
||||
annotations={annotations}
|
||||
onNew={model.onNew}
|
||||
@ -196,7 +190,6 @@ function AnnotationsSettingsEditView({
|
||||
onDelete,
|
||||
}: AnnotationsSettingsEditViewProps) {
|
||||
const { name, query } = annotationLayer.useState();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
const editAnnotationPageNav = {
|
||||
text: name,
|
||||
@ -204,13 +197,8 @@ function AnnotationsSettingsEditView({
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={editAnnotationPageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={editAnnotationPageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<AnnotationSettingsEdit
|
||||
annotation={query}
|
||||
editIndex={editIndex}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { NavModel, NavModelItem, PageLayoutType, arrayUtils } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
|
||||
import { DashboardLink } from '@grafana/schema';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { DashboardLinkForm } from '../settings/links/DashboardLinkForm';
|
||||
import { DashboardLinkList } from '../settings/links/DashboardLinkList';
|
||||
import { NEW_LINK } from '../settings/links/utils';
|
||||
@ -81,7 +80,6 @@ function DashboardLinksEditViewRenderer({ model }: SceneComponentProps<Dashboard
|
||||
const { links } = dashboard.useState();
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const linkToEdit = editIndex !== undefined ? links[editIndex] : undefined;
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
if (linkToEdit) {
|
||||
return (
|
||||
@ -97,13 +95,8 @@ function DashboardLinksEditViewRenderer({ model }: SceneComponentProps<Dashboard
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<DashboardLinkList
|
||||
links={links}
|
||||
onNew={model.onNewLink}
|
||||
@ -130,16 +123,10 @@ function EditLinkView({ pageNav, link, navModel, dashboard, onChange, onGoBack }
|
||||
text: 'Edit link',
|
||||
parentItem: pageNav,
|
||||
};
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={editLinkPageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={editLinkPageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<DashboardLinkForm link={link!} onUpdate={onChange} onGoBack={onGoBack} />
|
||||
</Page>
|
||||
);
|
||||
|
@ -25,7 +25,7 @@ import { GenAIDashTitleButton } from 'app/features/dashboard/components/GenAI/Ge
|
||||
|
||||
import { updateNavModel } from '../pages/utils';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
@ -177,16 +177,10 @@ export class GeneralSettingsEditView
|
||||
const { intervals } = model.getRefreshPicker().useState();
|
||||
const { hideTimeControls } = model.getDashboardControls().useState();
|
||||
const { enabled: liveNow } = model.getLiveNowTimer().useState();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
<Box marginBottom={5}>
|
||||
<Field
|
||||
|
@ -2,7 +2,6 @@ import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, sceneUtils } from '@grafana/scenes';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Alert, Box, Button, CodeEditor, Stack, useStyles2 } from '@grafana/ui';
|
||||
@ -19,7 +18,7 @@ import {
|
||||
} from '../saving/shared';
|
||||
import { useSaveDashboard } from '../saving/useSaveDashboard';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
@ -89,7 +88,6 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const canSave = dashboard.useState().meta.canSave;
|
||||
const { jsonText } = model.useState();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
const onSave = async (overwrite: boolean) => {
|
||||
const result = await onSaveDashboard(dashboard, JSON.parse(model.state.jsonText), {
|
||||
@ -176,13 +174,8 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div className={styles.wrapper}>
|
||||
<Trans i18nKey="dashboard-settings.json-editor.subtitle">
|
||||
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { PageLayoutType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
|
||||
import { Permissions } from 'app/core/components/AccessControl';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
@ -7,7 +6,7 @@ import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
|
||||
@ -35,16 +34,10 @@ function PermissionsEditorSettings({ model }: SceneComponentProps<PermissionsEdi
|
||||
const { uid } = dashboard.useState();
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const canSetPermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsWrite);
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<Permissions resource={'dashboards'} resourceId={uid ?? ''} canSetPermissions={canSetPermissions} />
|
||||
</Page>
|
||||
);
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneVariable, SceneVariables, sceneGraph } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync';
|
||||
@ -207,7 +206,6 @@ function VariableEditorSettingsListView({ model }: SceneComponentProps<Variables
|
||||
const { onDelete, onDuplicated, onOrderChanged, onEdit, onTypeChange, onGoBack, onAdd } = model;
|
||||
const { variables } = model.getVariableSet().useState();
|
||||
const { editIndex } = model.useState();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
if (editIndex !== undefined && variables[editIndex]) {
|
||||
const variable = variables[editIndex];
|
||||
@ -228,13 +226,8 @@ function VariableEditorSettingsListView({ model }: SceneComponentProps<Variables
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<VariableEditorList
|
||||
variables={variables}
|
||||
onDelete={onDelete}
|
||||
@ -269,20 +262,14 @@ function VariableEditorSettingsView({
|
||||
onValidateVariableName,
|
||||
}: VariableEditorSettingsEditViewProps) {
|
||||
const { name } = variable.useState();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
const editVariablePageNav = {
|
||||
text: name,
|
||||
parentItem: pageNav,
|
||||
};
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={editVariablePageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={editVariablePageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<VariableEditorForm
|
||||
variable={variable}
|
||||
onTypeChange={onTypeChange}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { PageLayoutType, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes';
|
||||
import { Spinner, Stack } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
|
||||
@ -189,7 +188,6 @@ function VersionsEditorSettingsListView({ model }: SceneComponentProps<VersionsE
|
||||
const showButtons = model.versions.length > 1;
|
||||
const hasMore = model.versions.length >= model.limit;
|
||||
const isLastPage = model.versions.find((rev) => rev.version === 1);
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
const viewModeCompare = (
|
||||
<>
|
||||
@ -239,13 +237,8 @@ function VersionsEditorSettingsListView({ model }: SceneComponentProps<VersionsE
|
||||
);
|
||||
|
||||
return (
|
||||
<Page
|
||||
navModel={navModel}
|
||||
pageNav={pageNav}
|
||||
layout={PageLayoutType.Standard}
|
||||
toolbar={isSingleTopNav ? <ToolbarActions dashboard={dashboard} /> : undefined}
|
||||
>
|
||||
{!isSingleTopNav && <NavToolbarActions dashboard={dashboard} />}
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
{viewMode === 'compare' ? viewModeCompare : viewModeList}
|
||||
</Page>
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
Badge,
|
||||
} from '@grafana/ui';
|
||||
import { updateNavIndex } from 'app/core/actions';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
|
||||
import config from 'app/core/config';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
@ -82,7 +83,6 @@ export const DashNav = memo<Props>((props) => {
|
||||
// this ensures the component rerenders when the location changes
|
||||
useLocation();
|
||||
const forceUpdate = useForceUpdate();
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
// We don't really care about the event payload here only that it triggeres a re-render of this component
|
||||
useBusEvent(props.dashboard.events, DashboardMetaChangedEvent);
|
||||
@ -357,11 +357,15 @@ export const DashNav = memo<Props>((props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderLeftActions()}
|
||||
{!isSingleTopNav && <NavToolbarSeparator leftActionsSeparator />}
|
||||
<ToolbarButtonRow alignment="right">{renderRightActions()}</ToolbarButtonRow>
|
||||
</>
|
||||
<AppChromeUpdate
|
||||
actions={
|
||||
<>
|
||||
{renderLeftActions()}
|
||||
<NavToolbarSeparator leftActionsSeparator />
|
||||
<ToolbarButtonRow alignment="right">{renderRightActions()}</ToolbarButtonRow>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -5,12 +5,12 @@ import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { SettingsPageProps } from '../DashboardSettings/types';
|
||||
|
||||
export const AccessControlDashboardPermissions = ({ dashboard, sectionNav, toolbar }: SettingsPageProps) => {
|
||||
export const AccessControlDashboardPermissions = ({ dashboard, sectionNav }: SettingsPageProps) => {
|
||||
const canSetPermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsWrite);
|
||||
const pageNav = sectionNav.node.parentItem;
|
||||
|
||||
return (
|
||||
<Page navModel={sectionNav} pageNav={pageNav} toolbar={toolbar}>
|
||||
<Page navModel={sectionNav} pageNav={pageNav}>
|
||||
<Permissions resource={'dashboards'} resourceId={dashboard.uid} canSetPermissions={canSetPermissions} />
|
||||
</Page>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } fro
|
||||
|
||||
import { SettingsPageProps } from './types';
|
||||
|
||||
export function AnnotationsSettings({ dashboard, editIndex, sectionNav, toolbar }: SettingsPageProps) {
|
||||
export function AnnotationsSettings({ dashboard, editIndex, sectionNav }: SettingsPageProps) {
|
||||
const onNew = () => {
|
||||
const newAnnotation: AnnotationQuery = {
|
||||
name: newAnnotationName,
|
||||
@ -27,7 +27,7 @@ export function AnnotationsSettings({ dashboard, editIndex, sectionNav, toolbar
|
||||
const isEditing = editIndex != null && editIndex < dashboard.annotations.list.length;
|
||||
|
||||
return (
|
||||
<Page toolbar={toolbar} navModel={sectionNav} pageNav={getSubPageNav(dashboard, editIndex, sectionNav.node)}>
|
||||
<Page navModel={sectionNav} pageNav={getSubPageNav(dashboard, editIndex, sectionNav.node)}>
|
||||
{!isEditing && <AnnotationSettingsList dashboard={dashboard} onNew={onNew} onEdit={onEdit} />}
|
||||
{isEditing && <AnnotationSettingsEdit dashboard={dashboard} editIdx={editIndex!} />}
|
||||
</Page>
|
||||
|
@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { locationUtil, NavModel, NavModelItem } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Button, Stack, Text, ToolbarButtonRow } from '@grafana/ui';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
@ -36,7 +36,6 @@ const onClose = () => locationService.partial({ editview: null, editIndex: null
|
||||
|
||||
export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }: Props) {
|
||||
const [updateId, setUpdateId] = useState(0);
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
useEffect(() => {
|
||||
dashboard.events.subscribe(DashboardMetaChangedEvent, () => setUpdateId((v) => v + 1));
|
||||
}, [dashboard]);
|
||||
@ -82,15 +81,8 @@ export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }:
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSingleTopNav && (
|
||||
<AppChromeUpdate actions={<ToolbarButtonRow alignment="right">{actions}</ToolbarButtonRow>} />
|
||||
)}
|
||||
<currentPage.component
|
||||
toolbar={isSingleTopNav ? <ToolbarButtonRow alignment="right">{actions}</ToolbarButtonRow> : undefined}
|
||||
sectionNav={subSectionNav}
|
||||
dashboard={dashboard}
|
||||
editIndex={editIndex}
|
||||
/>
|
||||
<AppChromeUpdate actions={<ToolbarButtonRow alignment="right">{actions}</ToolbarButtonRow>} />
|
||||
<currentPage.component sectionNav={subSectionNav} dashboard={dashboard} editIndex={editIndex} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -217,9 +209,9 @@ function getSectionNav(
|
||||
};
|
||||
}
|
||||
|
||||
function MakeEditable({ dashboard, sectionNav, toolbar }: SettingsPageProps) {
|
||||
function MakeEditable({ dashboard, sectionNav }: SettingsPageProps) {
|
||||
return (
|
||||
<Page navModel={sectionNav} toolbar={toolbar}>
|
||||
<Page navModel={sectionNav}>
|
||||
<Stack direction="column" gap={2} alignItems="flex-start">
|
||||
<Text variant="h3">Dashboard not editable</Text>
|
||||
<Button type="submit" onClick={() => dashboard.makeEditable()}>
|
||||
|
@ -39,7 +39,6 @@ export function GeneralSettingsUnconnected({
|
||||
updateTimeZone,
|
||||
updateWeekStart,
|
||||
sectionNav,
|
||||
toolbar,
|
||||
}: Props): JSX.Element {
|
||||
const [renderCounter, setRenderCounter] = useState(0);
|
||||
const [dashboardTitle, setDashboardTitle] = useState(dashboard.title);
|
||||
@ -120,7 +119,7 @@ export function GeneralSettingsUnconnected({
|
||||
];
|
||||
|
||||
return (
|
||||
<Page navModel={sectionNav} pageNav={pageNav} toolbar={toolbar}>
|
||||
<Page navModel={sectionNav} pageNav={pageNav}>
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
<Box marginBottom={5}>
|
||||
<Field
|
||||
|
@ -11,7 +11,7 @@ import { getDashboardSrv } from '../../services/DashboardSrv';
|
||||
|
||||
import { SettingsPageProps } from './types';
|
||||
|
||||
export function JsonEditorSettings({ dashboard, sectionNav, toolbar }: SettingsPageProps) {
|
||||
export function JsonEditorSettings({ dashboard, sectionNav }: SettingsPageProps) {
|
||||
const dashboardSaveModel = dashboard.getSaveModelClone();
|
||||
const [dashboardJson, setDashboardJson] = useState<string>(JSON.stringify(dashboardSaveModel, null, 2));
|
||||
const pageNav = sectionNav.node.parentItem;
|
||||
@ -24,7 +24,7 @@ export function JsonEditorSettings({ dashboard, sectionNav, toolbar }: SettingsP
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Page navModel={sectionNav} pageNav={pageNav} toolbar={toolbar}>
|
||||
<Page navModel={sectionNav} pageNav={pageNav}>
|
||||
<div className={styles.wrapper}>
|
||||
<Trans i18nKey="dashboard-settings.json-editor.subtitle">
|
||||
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, panel
|
||||
|
@ -10,7 +10,7 @@ import { SettingsPageProps } from './types';
|
||||
|
||||
export type LinkSettingsMode = 'list' | 'new' | 'edit';
|
||||
|
||||
export function LinksSettings({ dashboard, sectionNav, editIndex, toolbar }: SettingsPageProps) {
|
||||
export function LinksSettings({ dashboard, sectionNav, editIndex }: SettingsPageProps) {
|
||||
const [isNew, setIsNew] = useState<boolean>(false);
|
||||
|
||||
const onGoBack = () => {
|
||||
@ -44,7 +44,7 @@ export function LinksSettings({ dashboard, sectionNav, editIndex, toolbar }: Set
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navModel={sectionNav} pageNav={pageNav} toolbar={toolbar}>
|
||||
<Page navModel={sectionNav} pageNav={pageNav}>
|
||||
{!isEditing && <LinkSettingsList dashboard={dashboard} onNew={onNew} onEdit={onEdit} />}
|
||||
{isEditing && <LinkSettingsEdit dashboard={dashboard} editLinkIdx={editIndex} onGoBack={onGoBack} />}
|
||||
</Page>
|
||||
|
@ -143,7 +143,7 @@ export class VersionsSettings extends PureComponent<Props, State> {
|
||||
|
||||
if (viewMode === 'compare') {
|
||||
return (
|
||||
<Page navModel={this.props.sectionNav} pageNav={pageNav} toolbar={this.props.toolbar}>
|
||||
<Page navModel={this.props.sectionNav} pageNav={pageNav}>
|
||||
<VersionHistoryHeader
|
||||
onClick={this.reset}
|
||||
baseVersion={baseInfo?.version}
|
||||
@ -165,7 +165,7 @@ export class VersionsSettings extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navModel={this.props.sectionNav} pageNav={pageNav} toolbar={this.props.toolbar}>
|
||||
<Page navModel={this.props.sectionNav} pageNav={pageNav}>
|
||||
{isLoading ? (
|
||||
<VersionsHistorySpinner msg="Fetching history list…" />
|
||||
) : (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ComponentType, ReactNode } from 'react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { NavModel } from '@grafana/data';
|
||||
import { IconName } from '@grafana/ui';
|
||||
@ -17,5 +17,4 @@ export interface SettingsPageProps {
|
||||
dashboard: DashboardModel;
|
||||
sectionNav: NavModel;
|
||||
editIndex?: number;
|
||||
toolbar?: ReactNode;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { Subscription } from 'rxjs';
|
||||
|
||||
import { FieldConfigSource, GrafanaTheme2, NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import {
|
||||
Button,
|
||||
HorizontalGroup,
|
||||
@ -432,7 +432,6 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
|
||||
render() {
|
||||
const { initDone, uiState, theme, sectionNav, pageNav, className, updatePanelEditorUIState } = this.props;
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
const styles = getStyles(theme, this.props);
|
||||
|
||||
if (!initDone) {
|
||||
@ -446,17 +445,10 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
data-testid={selectors.components.PanelEditor.General.content}
|
||||
layout={PageLayoutType.Custom}
|
||||
className={className}
|
||||
toolbar={
|
||||
isSingleTopNav ? (
|
||||
<ToolbarButtonRow alignment="right">{this.renderEditorActions()}</ToolbarButtonRow>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{!isSingleTopNav && (
|
||||
<AppChromeUpdate
|
||||
actions={<ToolbarButtonRow alignment="right">{this.renderEditorActions()}</ToolbarButtonRow>}
|
||||
/>
|
||||
)}
|
||||
<AppChromeUpdate
|
||||
actions={<ToolbarButtonRow alignment="right">{this.renderEditorActions()}</ToolbarButtonRow>}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.verticalSplitPanesWrapper}>
|
||||
{!uiState.isPanelOptionsVisible ? (
|
||||
|
@ -7,7 +7,6 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import { Themeable2, withTheme2 } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { ScrollRefElement } from 'app/core/components/NativeScrollbar';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
|
||||
@ -362,7 +361,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
||||
const { editPanel, viewPanel, pageNav, sectionNav } = this.state;
|
||||
const kioskMode = getKioskMode(this.props.queryParams);
|
||||
const styles = getStyles(theme);
|
||||
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||
|
||||
if (!dashboard || !pageNav || !sectionNav) {
|
||||
return <DashboardLoading initPhase={this.props.initPhase} />;
|
||||
@ -438,8 +436,9 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
||||
layout={PageLayoutType.Canvas}
|
||||
className={pageClassName}
|
||||
onSetScrollRef={this.setScrollRef}
|
||||
toolbar={
|
||||
isSingleTopNav ? (
|
||||
>
|
||||
{showToolbar && (
|
||||
<header data-testid={selectors.pages.Dashboard.DashNav.navV2}>
|
||||
<DashNav
|
||||
dashboard={dashboard}
|
||||
title={dashboard.title}
|
||||
@ -448,23 +447,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
||||
kioskMode={kioskMode}
|
||||
hideTimePicker={dashboard.timepicker.hidden}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{showToolbar && (
|
||||
<header data-testid={selectors.pages.Dashboard.DashNav.navV2}>
|
||||
<AppChromeUpdate
|
||||
actions={
|
||||
<DashNav
|
||||
dashboard={dashboard}
|
||||
title={dashboard.title}
|
||||
folderTitle={dashboard.meta.folderTitle}
|
||||
isFullscreen={!!viewPanel}
|
||||
kioskMode={kioskMode}
|
||||
hideTimePicker={dashboard.timepicker.hidden}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
<DashboardPrompt dashboard={dashboard} />
|
||||
|
@ -105,14 +105,14 @@ class VariableEditorContainerUnconnected extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editIndex, variables, sectionNav, toolbar } = this.props;
|
||||
const { editIndex, variables, sectionNav } = this.props;
|
||||
const variableToEdit = editIndex != null ? variables[editIndex] : undefined;
|
||||
const node = sectionNav.node;
|
||||
const parentItem = node.parentItem;
|
||||
const subPageNav = variableToEdit ? { text: variableToEdit.name, parentItem } : parentItem;
|
||||
|
||||
return (
|
||||
<Page toolbar={toolbar} navModel={this.props.sectionNav} pageNav={subPageNav}>
|
||||
<Page navModel={this.props.sectionNav} pageNav={subPageNav}>
|
||||
{!variableToEdit && (
|
||||
<VariableEditorList
|
||||
variables={this.props.variables}
|
||||
|
Loading…
Reference in New Issue
Block a user