123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- import { css, cx } from '@emotion/css';
- import { useDialog } from '@react-aria/dialog';
- import { FocusScope } from '@react-aria/focus';
- import { useOverlay } from '@react-aria/overlays';
- import React, { useCallback, useMemo, useRef } from 'react';
- import { Link } from 'react-router-dom';
- import { GrafanaTheme2, locationUtil } from '@grafana/data';
- import { locationService, reportInteraction } from '@grafana/runtime';
- import { Button, CustomScrollbar, Icon, IconName, PageToolbar, stylesFactory, useForceUpdate } from '@grafana/ui';
- import config from 'app/core/config';
- import { contextSrv } from 'app/core/services/context_srv';
- import { AccessControlAction } from 'app/types';
- import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer';
- import { DashboardModel } from '../../state/DashboardModel';
- import { AccessControlDashboardPermissions } from '../DashboardPermissions/AccessControlDashboardPermissions';
- import { DashboardPermissions } from '../DashboardPermissions/DashboardPermissions';
- import { SaveDashboardAsButton, SaveDashboardButton } from '../SaveDashboard/SaveDashboardButton';
- import { AnnotationsSettings } from './AnnotationsSettings';
- import { GeneralSettings } from './GeneralSettings';
- import { JsonEditorSettings } from './JsonEditorSettings';
- import { LinksSettings } from './LinksSettings';
- import { VersionsSettings } from './VersionsSettings';
- export interface Props {
- dashboard: DashboardModel;
- editview: string;
- }
- export interface SettingsPage {
- id: string;
- title: string;
- icon: IconName;
- component: React.ReactNode;
- }
- const onClose = () => locationService.partial({ editview: null });
- const MakeEditable = (props: { onMakeEditable: () => any }) => (
- <div>
- <div className="dashboard-settings__header">Dashboard not editable</div>
- <Button type="submit" onClick={props.onMakeEditable}>
- Make editable
- </Button>
- </div>
- );
- export function DashboardSettings({ dashboard, editview }: Props) {
- const ref = useRef<HTMLDivElement>(null);
- const { overlayProps } = useOverlay(
- {
- isOpen: true,
- onClose,
- },
- ref
- );
- const { dialogProps } = useDialog(
- {
- 'aria-label': 'Dashboard settings',
- },
- ref
- );
- const forceUpdate = useForceUpdate();
- const onMakeEditable = useCallback(() => {
- dashboard.editable = true;
- dashboard.meta.canMakeEditable = false;
- dashboard.meta.canEdit = true;
- dashboard.meta.canSave = true;
- forceUpdate();
- }, [dashboard, forceUpdate]);
- const pages = useMemo((): SettingsPage[] => {
- const pages: SettingsPage[] = [];
- if (dashboard.meta.canEdit) {
- pages.push({
- title: 'General',
- id: 'settings',
- icon: 'sliders-v-alt',
- component: <GeneralSettings dashboard={dashboard} />,
- });
- pages.push({
- title: 'Annotations',
- id: 'annotations',
- icon: 'comment-alt',
- component: <AnnotationsSettings dashboard={dashboard} />,
- });
- pages.push({
- title: 'Variables',
- id: 'templating',
- icon: 'calculator-alt',
- component: <VariableEditorContainer dashboard={dashboard} />,
- });
- pages.push({
- title: 'Links',
- id: 'links',
- icon: 'link',
- component: <LinksSettings dashboard={dashboard} />,
- });
- }
- if (dashboard.meta.canMakeEditable) {
- pages.push({
- title: 'General',
- icon: 'sliders-v-alt',
- id: 'settings',
- component: <MakeEditable onMakeEditable={onMakeEditable} />,
- });
- }
- if (dashboard.id && dashboard.meta.canSave) {
- pages.push({
- title: 'Versions',
- id: 'versions',
- icon: 'history',
- component: <VersionsSettings dashboard={dashboard} />,
- });
- }
- if (dashboard.id && dashboard.meta.canAdmin) {
- if (!config.rbacEnabled) {
- pages.push({
- title: 'Permissions',
- id: 'permissions',
- icon: 'lock',
- component: <DashboardPermissions dashboard={dashboard} />,
- });
- } else if (contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsRead)) {
- pages.push({
- title: 'Permissions',
- id: 'permissions',
- icon: 'lock',
- component: <AccessControlDashboardPermissions dashboard={dashboard} />,
- });
- }
- }
- pages.push({
- title: 'JSON Model',
- id: 'dashboard_json',
- icon: 'arrow',
- component: <JsonEditorSettings dashboard={dashboard} />,
- });
- return pages;
- }, [dashboard, onMakeEditable]);
- const onPostSave = () => {
- dashboard.meta.hasUnsavedFolderChange = false;
- };
- const folderTitle = dashboard.meta.folderTitle;
- const currentPage = pages.find((page) => page.id === editview) ?? pages[0];
- const canSaveAs = contextSrv.hasEditPermissionInFolders;
- const canSave = dashboard.meta.canSave;
- const styles = getStyles(config.theme2);
- return (
- <FocusScope contain autoFocus>
- <div className="dashboard-settings" ref={ref} {...overlayProps} {...dialogProps}>
- <PageToolbar title={`${dashboard.title} / Settings`} parent={folderTitle} onGoBack={onClose} />
- <CustomScrollbar>
- <div className={styles.scrollInner}>
- <div className={styles.settingsWrapper}>
- <aside className="dashboard-settings__aside">
- {pages.map((page) => (
- <Link
- onClick={() => reportInteraction(`Dashboard settings navigation to ${page.id}`)}
- to={(loc) => locationUtil.getUrlForPartial(loc, { editview: page.id })}
- className={cx('dashboard-settings__nav-item', { active: page.id === editview })}
- key={page.id}
- >
- <Icon name={page.icon} style={{ marginRight: '4px' }} />
- {page.title}
- </Link>
- ))}
- <div className="dashboard-settings__aside-actions">
- {canSave && <SaveDashboardButton dashboard={dashboard} onSaveSuccess={onPostSave} />}
- {canSaveAs && (
- <SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={onPostSave} variant="secondary" />
- )}
- </div>
- </aside>
- <div className={styles.settingsContent}>{currentPage.component}</div>
- </div>
- </div>
- </CustomScrollbar>
- </div>
- </FocusScope>
- );
- }
- const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
- scrollInner: css`
- min-width: 100%;
- display: flex;
- `,
- settingsWrapper: css`
- margin: ${theme.spacing(0, 2, 2)};
- display: flex;
- flex-grow: 1;
- `,
- settingsContent: css`
- flex-grow: 1;
- height: 100%;
- padding: 32px;
- border: 1px solid ${theme.colors.border.weak};
- background: ${theme.colors.background.primary};
- border-radius: ${theme.shape.borderRadius()};
- `,
- }));
|