mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardSave: fix save dashboard when changes are detected (#23909)
This commit is contained in:
parent
5211a23f8d
commit
9ac7263e66
@ -7,21 +7,6 @@ import { stylesFactory, ThemeContext } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { HorizontalGroup } from '..';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
modal: css`
|
||||
width: 500px;
|
||||
`,
|
||||
modalContent: css`
|
||||
text-align: center;
|
||||
`,
|
||||
modalText: css`
|
||||
font-size: ${theme.typography.heading.h4};
|
||||
color: ${theme.colors.link};
|
||||
margin-bottom: calc(${theme.spacing.d} * 2);
|
||||
padding-top: ${theme.spacing.d};
|
||||
`,
|
||||
}));
|
||||
|
||||
const defaultIcon: IconName = 'exclamation-triangle';
|
||||
|
||||
interface Props {
|
||||
@ -64,3 +49,18 @@ export const ConfirmModal: FC<Props> = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
modal: css`
|
||||
width: 500px;
|
||||
`,
|
||||
modalContent: css`
|
||||
text-align: center;
|
||||
`,
|
||||
modalText: css`
|
||||
font-size: ${theme.typography.heading.h4};
|
||||
color: ${theme.colors.link};
|
||||
margin-bottom: calc(${theme.spacing.d} * 2);
|
||||
padding-top: ${theme.spacing.d};
|
||||
`,
|
||||
}));
|
||||
|
@ -76,9 +76,10 @@ export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation' | 'wrap'>>
|
||||
children,
|
||||
spacing,
|
||||
justify,
|
||||
align,
|
||||
width,
|
||||
}) => (
|
||||
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} width={width}>
|
||||
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} align={align} width={width}>
|
||||
{children}
|
||||
</Layout>
|
||||
);
|
||||
@ -92,7 +93,11 @@ export const Container: React.FC<ContainerProps> = ({ children, padding, margin
|
||||
const getStyles = stylesFactory(
|
||||
(theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align, wrap) => {
|
||||
const finalSpacing = spacing !== 'none' ? theme.spacing[spacing] : 0;
|
||||
const marginCompensation = orientation === Orientation.Horizontal && !wrap ? 0 : `-${finalSpacing}`;
|
||||
// compensate for last row margin when wrapped, horizontal layout
|
||||
const marginCompensation =
|
||||
(orientation === Orientation.Horizontal && !wrap) || orientation === Orientation.Vertical
|
||||
? 0
|
||||
: `-${finalSpacing}`;
|
||||
|
||||
return {
|
||||
layout: css`
|
||||
|
@ -14,15 +14,9 @@ interface SaveDashboardButtonProps {
|
||||
*/
|
||||
getDashboard?: () => DashboardModel;
|
||||
onSaveSuccess?: () => void;
|
||||
useNewForms?: boolean;
|
||||
}
|
||||
|
||||
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({
|
||||
dashboard,
|
||||
onSaveSuccess,
|
||||
getDashboard,
|
||||
useNewForms,
|
||||
}) => {
|
||||
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ dashboard, onSaveSuccess, getDashboard }) => {
|
||||
return (
|
||||
<ModalsController>
|
||||
{({ showModal, hideModal }) => {
|
||||
|
@ -9,6 +9,7 @@ import { SaveDashboardModalProps } from './types';
|
||||
export const SaveDashboardModal: React.FC<SaveDashboardModalProps> = ({ dashboard, onDismiss, onSaveSuccess }) => {
|
||||
const { state, onDashboardSave } = useDashboardSave(dashboard);
|
||||
const [dashboardSaveModelClone, setDashboardSaveModelClone] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.error && (
|
||||
|
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { Button, HorizontalGroup, Modal, VerticalGroup } from '@grafana/ui';
|
||||
import { SaveDashboardButton } from './SaveDashboardButton';
|
||||
import { DashboardModel } from '../../state';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface UnsavedChangesModalProps {
|
||||
dashboard: DashboardModel;
|
||||
onDiscard: () => void;
|
||||
onDismiss: () => void;
|
||||
onSaveSuccess?: () => void;
|
||||
}
|
||||
|
||||
export const UnsavedChangesModal: React.FC<UnsavedChangesModalProps> = ({
|
||||
dashboard,
|
||||
onSaveSuccess,
|
||||
onDiscard,
|
||||
onDismiss,
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
title="Unsaved changes"
|
||||
onDismiss={onDismiss}
|
||||
icon="exclamation-triangle"
|
||||
className={css`
|
||||
width: 500px;
|
||||
`}
|
||||
>
|
||||
<VerticalGroup align={'center'} spacing={'md'}>
|
||||
<h4>Do you want to save your changes?</h4>
|
||||
<HorizontalGroup justify="center">
|
||||
<SaveDashboardButton dashboard={dashboard} onSaveSuccess={onSaveSuccess} />
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
onDiscard();
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
Discard
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onDismiss}>
|
||||
Cancel
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</VerticalGroup>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
const template = `
|
||||
<div class="modal-body">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-header-title">
|
||||
<icon name="'exclamation-triangle'" size="'lg'"></icon>
|
||||
<span class="p-l-1">Unsaved changes</span>
|
||||
</h2>
|
||||
|
||||
<a class="modal-header-close" ng-click="ctrl.dismiss();">
|
||||
<icon name="'times'"></icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modal-content text-center">
|
||||
|
||||
<div class="confirm-modal-text">
|
||||
Do you want to save your changes?
|
||||
</div>
|
||||
|
||||
<div class="confirm-modal-buttons">
|
||||
<save-dashboard-button dashboard="ctrl.unsavedChangesSrv.tracker.current" onSaveSuccess="ctrl.onSaveSuccess" >Save</save-dashboard-button>
|
||||
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
|
||||
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export class UnsavedChangesModalCtrl {
|
||||
clone: any;
|
||||
dismiss: () => void;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private unsavedChangesSrv: any) {}
|
||||
|
||||
discard() {
|
||||
this.dismiss();
|
||||
this.unsavedChangesSrv.tracker.discardChanges();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dismiss();
|
||||
this.unsavedChangesSrv.tracker.saveChanges();
|
||||
}
|
||||
|
||||
onSaveSuccess = () => {
|
||||
this.dismiss();
|
||||
this.unsavedChangesSrv.tracker.onSaveSuccess();
|
||||
};
|
||||
}
|
||||
|
||||
export function unsavedChangesModalDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
controller: UnsavedChangesModalCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: { dismiss: '&' },
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('unsavedChangesModal', unsavedChangesModalDirective);
|
@ -1 +0,0 @@
|
||||
export { UnsavedChangesModalCtrl } from './UnsavedChangesModalCtrl';
|
@ -10,7 +10,6 @@ import './components/DashNav';
|
||||
import './components/VersionHistory';
|
||||
import './components/DashboardSettings';
|
||||
import './components/SubMenu';
|
||||
import './components/UnsavedChangesModal';
|
||||
import './components/AdHocFilters';
|
||||
import './components/RowOptions';
|
||||
|
||||
|
@ -5,6 +5,8 @@ import { ContextSrv } from 'app/core/services/context_srv';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { AppEventConsumer, CoreEvents } from 'app/types';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { UnsavedChangesModal } from '../components/SaveDashboard/UnsavedChangesModal';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
|
||||
export class ChangeTracker {
|
||||
current: any;
|
||||
@ -159,17 +161,23 @@ export class ChangeTracker {
|
||||
return currentJson !== originalJson;
|
||||
}
|
||||
|
||||
discardChanges() {
|
||||
discardChanges = () => {
|
||||
this.original = null;
|
||||
this.gotoNext();
|
||||
}
|
||||
};
|
||||
|
||||
open_modal() {
|
||||
this.$rootScope.appEvent(CoreEvents.showModal, {
|
||||
templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
|
||||
modalClass: 'modal--narrow confirm-modal',
|
||||
open_modal = () => {
|
||||
this.$rootScope.appEvent(CoreEvents.showModalReact, {
|
||||
component: UnsavedChangesModal,
|
||||
props: {
|
||||
dashboard: this.current,
|
||||
onSaveSuccess: this.onSaveSuccess,
|
||||
onDiscard: () => {
|
||||
this.discardChanges();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSaveSuccess = () => {
|
||||
this.$timeout(() => {
|
||||
@ -177,9 +185,11 @@ export class ChangeTracker {
|
||||
});
|
||||
};
|
||||
|
||||
gotoNext() {
|
||||
gotoNext = () => {
|
||||
const baseLen = this.$location.absUrl().length - this.$location.url().length;
|
||||
const nextUrl = this.next.substring(baseLen);
|
||||
this.$location.url(nextUrl);
|
||||
}
|
||||
getLocationSrv().update({
|
||||
path: nextUrl,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user