mirror of
https://github.com/grafana/grafana.git
synced 2024-12-30 10:47:30 -06:00
Dashboard: Migrate general settings to react (#30914)
* feat(dashboard): initial commit of general settings migration to react * fix(dashboardsettings): force update of general settings when selects change * feat(dashboardsettings): initial commit of delete dashboard button and modal * feat(dashboardsettings): introduce useDashboardDelete hook * feat(dashboardsettings): add tags and editable inputs * refactor(dashboardsettings): fix typescript error in general settings * refactor(dashboardsettings): use grafana-ui form components for general settings * refactor(dashboardsettings): use ConfirmModal and move provisioned modal to own component * refactor(dashboardsettings): revertDashboardModal uses ConfirmModal * test(autorefreshintervals): remove renderCount prop to fix test * test(dashboardsettings): put back aria-label for e2e tests * chore(dashboardsettings): remove redundant generl settings angular code * test: change references to now deleted SettingsCtrl to GeneralSettings * refactor(dashboardsettings): swap out switch for inlineswitch component * chore(timepickersettings): remove timePickerSettings angular directive definition * feat(dashboardsettings): add tooltips, fix description field name * refactor(dashboardsettings): remove redundant await Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * refactor(usedashboarddelete): clean up Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
a2cca8d488
commit
ef8a5b760f
@ -25,7 +25,6 @@ import { HelpModal } from './components/help/HelpModal';
|
||||
import { Footer } from './components/Footer/Footer';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { SearchField, SearchResults, SearchResultsFilter, SearchWrapper } from '../features/search';
|
||||
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
|
||||
|
||||
const { SecretFormField } = LegacyForms;
|
||||
|
||||
@ -185,15 +184,4 @@ export function registerAngularDirectives() {
|
||||
['onLoad', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('timePickerSettings', TimePickerSettings, [
|
||||
'renderCount',
|
||||
'refreshIntervals',
|
||||
'timePickerHidden',
|
||||
'nowDelay',
|
||||
'timezone',
|
||||
['onTimeZoneChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onRefreshIntervalChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onNowDelayChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onHideTimePickerChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { TimeSrv } from '../../services/TimeSrv';
|
||||
|
||||
const setupTestContext = (options: Partial<Props>) => {
|
||||
const defaults: Props = {
|
||||
renderCount: 0,
|
||||
refreshIntervals: ['1s', '5s', '10s'],
|
||||
onRefreshIntervalChange: jest.fn(),
|
||||
getIntervalsFunc: (intervals) => intervals,
|
||||
|
@ -4,7 +4,6 @@ import { Input, Tooltip, defaultIntervals } from '@grafana/ui';
|
||||
import { getTimeSrv } from '../../services/TimeSrv';
|
||||
|
||||
export interface Props {
|
||||
renderCount: number; // hack to make sure Angular changes are propagated properly, please remove when DashboardSettings are migrated to React
|
||||
refreshIntervals: string[];
|
||||
onRefreshIntervalChange: (interval: string[]) => void;
|
||||
getIntervalsFunc?: typeof getValidIntervals;
|
||||
@ -12,7 +11,6 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const AutoRefreshIntervals: FC<Props> = ({
|
||||
renderCount,
|
||||
refreshIntervals,
|
||||
onRefreshIntervalChange,
|
||||
getIntervalsFunc = getValidIntervals,
|
||||
@ -24,7 +22,7 @@ export const AutoRefreshIntervals: FC<Props> = ({
|
||||
useEffect(() => {
|
||||
const intervals = getIntervalsFunc(refreshIntervals ?? defaultIntervals);
|
||||
setIntervals(intervals);
|
||||
}, [renderCount, refreshIntervals]);
|
||||
}, [refreshIntervals]);
|
||||
|
||||
const intervalsString = useMemo(() => {
|
||||
if (!Array.isArray(intervals)) {
|
||||
|
@ -1,30 +1,122 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { SelectableValue, TimeZone } from '@grafana/data';
|
||||
import { Select, InlineSwitch, TagsInput, InlineField, Input } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
import { DeleteDashboardButton } from '../DeleteDashboard/DeleteDashboardButton';
|
||||
import { TimePickerSettings } from './TimePickerSettings';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class GeneralSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp?: AngularComponent;
|
||||
const GRAPH_TOOLTIP_OPTIONS = [
|
||||
{ value: 0, label: 'Default' },
|
||||
{ value: 1, label: 'Shared crosshair' },
|
||||
{ value: 2, label: 'Shared Tooltip' },
|
||||
];
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
export const GeneralSettings: React.FC<Props> = ({ dashboard }) => {
|
||||
const [renderCounter, setRenderCounter] = useState(0);
|
||||
|
||||
const template = '<dashboard-settings dashboard="dashboard" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
const onFolderChange = (folder: { id: number; title: string }) => {
|
||||
dashboard.meta.folderId = folder.id;
|
||||
dashboard.meta.folderTitle = folder.title;
|
||||
dashboard.meta.hasUnsavedFolderChange = true;
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||
dashboard[event.currentTarget.name as 'title' | 'description'] = event.currentTarget.value;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div ref={(ref) => (this.element = ref)} />;
|
||||
}
|
||||
}
|
||||
const onTooltipChange = (graphTooltip: SelectableValue<number>) => {
|
||||
dashboard.graphTooltip = graphTooltip.value;
|
||||
setRenderCounter(renderCounter + 1);
|
||||
};
|
||||
|
||||
const onRefreshIntervalChange = (intervals: string[]) => {
|
||||
dashboard.timepicker.refresh_intervals = intervals.filter((i) => i.trim() !== '');
|
||||
};
|
||||
|
||||
const onNowDelayChange = (nowDelay: string) => {
|
||||
dashboard.timepicker.nowDelay = nowDelay;
|
||||
};
|
||||
|
||||
const onHideTimePickerChange = (hide: boolean) => {
|
||||
dashboard.timepicker.hidden = hide;
|
||||
setRenderCounter(renderCounter + 1);
|
||||
};
|
||||
|
||||
const onTimeZoneChange = (timeZone: TimeZone) => {
|
||||
dashboard.timezone = timeZone;
|
||||
setRenderCounter(renderCounter + 1);
|
||||
};
|
||||
|
||||
const onTagsChange = (tags: string[]) => {
|
||||
dashboard.tags = tags;
|
||||
};
|
||||
|
||||
const onEditableChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
dashboard.editable = ev.currentTarget.checked;
|
||||
setRenderCounter(renderCounter + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="dashboard-settings__header" aria-label={selectors.pages.Dashboard.Settings.General.title}>
|
||||
General
|
||||
</h3>
|
||||
<div className="gf-form-group">
|
||||
<InlineField label="Name" labelWidth={14}>
|
||||
<Input name="title" onBlur={onBlur} defaultValue={dashboard.title} width={60} />
|
||||
</InlineField>
|
||||
<InlineField label="Description" labelWidth={14}>
|
||||
<Input name="description" onBlur={onBlur} defaultValue={dashboard.description} width={60} />
|
||||
</InlineField>
|
||||
<InlineField label="Tags" tooltip="Press enter to add a tag" labelWidth={14}>
|
||||
<TagsInput tags={dashboard.tags} onChange={onTagsChange} />
|
||||
</InlineField>
|
||||
<FolderPicker
|
||||
initialTitle={dashboard.meta.folderTitle}
|
||||
initialFolderId={dashboard.meta.folderId}
|
||||
onChange={onFolderChange}
|
||||
enableCreateNew={true}
|
||||
dashboardId={dashboard.id}
|
||||
/>
|
||||
<InlineField
|
||||
label="Editable"
|
||||
tooltip="Uncheck, then save and reload to disable all dashboard editing"
|
||||
labelWidth={14}
|
||||
>
|
||||
<InlineSwitch value={dashboard.editable} onChange={onEditableChange} />
|
||||
</InlineField>
|
||||
</div>
|
||||
<TimePickerSettings
|
||||
onTimeZoneChange={onTimeZoneChange}
|
||||
onRefreshIntervalChange={onRefreshIntervalChange}
|
||||
onNowDelayChange={onNowDelayChange}
|
||||
onHideTimePickerChange={onHideTimePickerChange}
|
||||
refreshIntervals={dashboard.timepicker.refresh_intervals}
|
||||
timePickerHidden={dashboard.timepicker.hidden}
|
||||
nowDelay={dashboard.timepicker.nowDelay}
|
||||
timezone={dashboard.timezone}
|
||||
/>
|
||||
|
||||
<h5 className="section-heading">Panel Options</h5>
|
||||
<div className="gf-form">
|
||||
<InlineField label="Graph Tooltip" labelWidth={14}>
|
||||
<Select
|
||||
onChange={onTooltipChange}
|
||||
options={GRAPH_TOOLTIP_OPTIONS}
|
||||
width={40}
|
||||
value={dashboard.graphTooltip}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
{dashboard.meta.canSave && <DeleteDashboardButton dashboard={dashboard} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,125 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { appEvents, coreModule } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { AppEvents, TimeZone } from '@grafana/data';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
import { deleteDashboard } from 'app/features/manage-dashboards/state/actions';
|
||||
|
||||
export class SettingsCtrl {
|
||||
dashboard: DashboardModel;
|
||||
canSaveAs: boolean;
|
||||
canSave?: boolean;
|
||||
canDelete?: boolean;
|
||||
selectors: typeof selectors.pages.Dashboard.Settings.General;
|
||||
renderCount: number; // hack to update React when Angular changes
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope: IScope & Record<string, any>, private $location: ILocationService) {
|
||||
// temp hack for annotations and variables editors
|
||||
// that rely on inherited scope
|
||||
$scope.dashboard = this.dashboard;
|
||||
this.canDelete = this.dashboard.meta.canSave;
|
||||
this.selectors = selectors.pages.Dashboard.Settings.General;
|
||||
this.renderCount = 0;
|
||||
}
|
||||
|
||||
deleteDashboard() {
|
||||
let confirmText = '';
|
||||
let text2 = this.dashboard.title;
|
||||
|
||||
if (this.dashboard.meta.provisioned) {
|
||||
appEvents.emit(CoreEvents.showConfirmModal, {
|
||||
title: 'Cannot delete provisioned dashboard',
|
||||
text: `
|
||||
This dashboard is managed by Grafanas provisioning and cannot be deleted. Remove the dashboard from the
|
||||
config file to delete it.
|
||||
`,
|
||||
text2: `
|
||||
<i>See <a class="external-link" href="http://docs.grafana.org/administration/provisioning/#dashboards" target="_blank">
|
||||
documentation</a> for more information about provisioning.</i>
|
||||
</br>
|
||||
File path: ${this.dashboard.meta.provisionedExternalId}
|
||||
`,
|
||||
text2htmlBind: true,
|
||||
icon: 'trash-alt',
|
||||
noText: 'OK',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const alerts = _.sumBy(this.dashboard.panels, (panel) => {
|
||||
return panel.alert ? 1 : 0;
|
||||
});
|
||||
|
||||
if (alerts > 0) {
|
||||
confirmText = 'DELETE';
|
||||
text2 = `This dashboard contains ${alerts} alerts. Deleting this dashboard will also delete those alerts`;
|
||||
}
|
||||
|
||||
appEvents.emit(CoreEvents.showConfirmModal, {
|
||||
title: 'Delete',
|
||||
text: 'Do you want to delete this dashboard?',
|
||||
text2: text2,
|
||||
icon: 'trash-alt',
|
||||
confirmText: confirmText,
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
this.dashboard.meta.canSave = false;
|
||||
this.deleteDashboardConfirmed();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deleteDashboardConfirmed() {
|
||||
promiseToDigest(this.$scope)(
|
||||
deleteDashboard(this.dashboard.uid, false).then(() => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
||||
this.$location.url('/');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onFolderChange = (folder: { id: number; title: string }) => {
|
||||
this.dashboard.meta.folderId = folder.id;
|
||||
this.dashboard.meta.folderTitle = folder.title;
|
||||
this.dashboard.meta.hasUnsavedFolderChange = true;
|
||||
};
|
||||
|
||||
onRefreshIntervalChange = (intervals: string[]) => {
|
||||
this.dashboard.timepicker.refresh_intervals = intervals.filter((i) => i.trim() !== '');
|
||||
this.renderCount++;
|
||||
};
|
||||
|
||||
onNowDelayChange = (nowDelay: string) => {
|
||||
this.dashboard.timepicker.nowDelay = nowDelay;
|
||||
this.renderCount++;
|
||||
};
|
||||
|
||||
onHideTimePickerChange = (hide: boolean) => {
|
||||
this.dashboard.timepicker.hidden = hide;
|
||||
this.renderCount++;
|
||||
};
|
||||
|
||||
onTimeZoneChange = (timeZone: TimeZone) => {
|
||||
this.dashboard.timezone = timeZone;
|
||||
this.renderCount++;
|
||||
};
|
||||
}
|
||||
|
||||
export function dashboardSettings() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'public/app/features/dashboard/components/DashboardSettings/template.html',
|
||||
controller: SettingsCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
transclude: true,
|
||||
scope: { dashboard: '=' },
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('dashboardSettings', dashboardSettings);
|
@ -1,5 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { InlineField, Input, Switch, TimeZonePicker, Tooltip } from '@grafana/ui';
|
||||
import { InlineField, Input, InlineSwitch, TimeZonePicker, Tooltip } from '@grafana/ui';
|
||||
import { rangeUtil, TimeZone } from '@grafana/data';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
@ -10,7 +10,6 @@ interface Props {
|
||||
onRefreshIntervalChange: (interval: string[]) => void;
|
||||
onNowDelayChange: (nowDelay: string) => void;
|
||||
onHideTimePickerChange: (hide: boolean) => void;
|
||||
renderCount: number; // hack to make sure Angular changes are propagated properly, please remove when DashboardSettings are migrated to React
|
||||
refreshIntervals: string[];
|
||||
timePickerHidden: boolean;
|
||||
nowDelay: string;
|
||||
@ -66,7 +65,6 @@ export class TimePickerSettings extends PureComponent<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<AutoRefreshIntervals
|
||||
renderCount={this.props.renderCount}
|
||||
refreshIntervals={this.props.refreshIntervals}
|
||||
onRefreshIntervalChange={this.props.onRefreshIntervalChange}
|
||||
/>
|
||||
@ -88,7 +86,7 @@ export class TimePickerSettings extends PureComponent<Props, State> {
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineField labelWidth={14} label="Hide time picker">
|
||||
<Switch value={!!this.props.timePickerHidden} onChange={this.onHideTimePickerChange} />
|
||||
<InlineSwitch value={!!this.props.timePickerHidden} onChange={this.onHideTimePickerChange} />
|
||||
</InlineField>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1 @@
|
||||
export { SettingsCtrl } from './SettingsCtrl';
|
||||
export { DashboardSettings } from './DashboardSettings';
|
||||
export { TimePickerSettings } from './TimePickerSettings';
|
||||
|
@ -1,64 +0,0 @@
|
||||
|
||||
<h3 class="dashboard-settings__header" aria-label="{{::ctrl.selectors.title}}">
|
||||
General
|
||||
</h3>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Name</label>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Description</label>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.description'></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">
|
||||
Tags
|
||||
<info-popover mode="right-normal">Press enter to add a tag</info-popover>
|
||||
</label>
|
||||
<bootstrap-tagsinput ng-model="ctrl.dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
</div>
|
||||
<folder-picker initial-title="ctrl.dashboard.meta.folderTitle" initial-folder-id="ctrl.dashboard.meta.folderId"
|
||||
on-change="ctrl.onFolderChange" enable-create-new="true" is-valid-selection="true" label-class="width-7"
|
||||
dashboard-id="ctrl.dashboard.id">
|
||||
</folder-picker>
|
||||
<gf-form-switch class="gf-form" label="Editable"
|
||||
tooltip="Uncheck, then save and reload to disable all dashboard editing" checked="ctrl.dashboard.editable"
|
||||
label-class="width-7">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<time-picker-settings
|
||||
onTimeZoneChange="ctrl.onTimeZoneChange"
|
||||
onRefreshIntervalChange="ctrl.onRefreshIntervalChange"
|
||||
onNowDelayChange="ctrl.onNowDelayChange"
|
||||
onHideTimePickerChange="ctrl.onHideTimePickerChange"
|
||||
renderCount="ctrl.renderCount"
|
||||
refreshIntervals="ctrl.dashboard.timepicker.refresh_intervals"
|
||||
timePickerHidden="ctrl.dashboard.timepicker.hidden"
|
||||
nowDelay="ctrl.dashboard.timepicker.nowDelay"
|
||||
timezone="ctrl.dashboard.timezone"
|
||||
>
|
||||
</time-picker-settings>
|
||||
|
||||
<h5 class="section-heading">Panel Options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-11">
|
||||
Graph Tooltip
|
||||
<info-popover mode="right-normal">
|
||||
Cycle between options using Shortcut: CTRL+O or CMD+O
|
||||
</info-popover>
|
||||
</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="ctrl.dashboard.graphTooltip" class='gf-form-input'
|
||||
ng-options="f.value as f.text for f in [{value: 0, text: 'Default'}, {value: 1, text: 'Shared crosshair'},{value: 2, text: 'Shared Tooltip'}]"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-danger" ng-click="ctrl.deleteDashboard()" ng-show="ctrl.canDelete"
|
||||
aria-label="Dashboard settings page delete dashboard button">
|
||||
Delete Dashboard
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { DeleteDashboardModal } from './DeleteDashboardModal';
|
||||
import { Button, ModalsController } from '@grafana/ui';
|
||||
import { DashboardModel } from '../../state';
|
||||
|
||||
type Props = {
|
||||
dashboard: DashboardModel;
|
||||
};
|
||||
|
||||
export const DeleteDashboardButton = ({ dashboard }: Props) => (
|
||||
<ModalsController>
|
||||
{({ showModal, hideModal }) => (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
showModal(DeleteDashboardModal, {
|
||||
dashboard,
|
||||
hideModal,
|
||||
});
|
||||
}}
|
||||
aria-label="Dashboard settings page delete dashboard button"
|
||||
>
|
||||
Delete Dashboard
|
||||
</Button>
|
||||
)}
|
||||
</ModalsController>
|
||||
);
|
@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import sumBy from 'lodash/sumBy';
|
||||
import { Modal, ConfirmModal, HorizontalGroup, Button } from '@grafana/ui';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { useDashboardDelete } from './useDashboardDelete';
|
||||
|
||||
type DeleteDashboardModalProps = {
|
||||
hideModal(): void;
|
||||
dashboard: DashboardModel;
|
||||
};
|
||||
|
||||
export const DeleteDashboardModal: React.FC<DeleteDashboardModalProps> = ({ hideModal, dashboard }) => {
|
||||
const isProvisioned = dashboard.meta.provisioned;
|
||||
const { onRestoreDashboard } = useDashboardDelete(dashboard.uid);
|
||||
const modalBody = getModalBody(dashboard.panels, dashboard.title);
|
||||
|
||||
if (isProvisioned) {
|
||||
return <ProvisionedDeleteModal hideModal={hideModal} provisionedId={dashboard.meta.provisionedExternalId!} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
isOpen={true}
|
||||
body={modalBody}
|
||||
onConfirm={onRestoreDashboard}
|
||||
onDismiss={hideModal}
|
||||
title="Delete"
|
||||
icon="trash-alt"
|
||||
confirmText="Delete"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getModalBody = (panels: PanelModel[], title: string) => {
|
||||
const totalAlerts = sumBy(panels, (panel) => (panel.alert ? 1 : 0));
|
||||
return totalAlerts > 0 ? (
|
||||
<>
|
||||
<p>Do you want to delete this dashboard?</p>
|
||||
<p>
|
||||
This dashboard contains {totalAlerts} alert{totalAlerts > 1 ? 's' : ''}. Deleting this dashboard will also
|
||||
delete those alerts
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>Do you want to delete this dashboard?</p>
|
||||
<p>{title}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProvisionedDeleteModal = ({ hideModal, provisionedId }: { hideModal(): void; provisionedId: string }) => (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
title="Cannot delete provisioned dashboard"
|
||||
icon="trash-alt"
|
||||
onDismiss={hideModal}
|
||||
className={css`
|
||||
text-align: center;
|
||||
width: 500px;
|
||||
`}
|
||||
>
|
||||
<p>
|
||||
This dashboard is managed by Grafanas provisioning and cannot be deleted. Remove the dashboard from the config
|
||||
file to delete it.
|
||||
</p>
|
||||
<p>
|
||||
<i>
|
||||
See{' '}
|
||||
<a
|
||||
className="external-link"
|
||||
href="http://docs.grafana.org/administration/provisioning/#dashboards"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
documentation
|
||||
</a>{' '}
|
||||
for more information about provisioning.
|
||||
</i>
|
||||
<br />
|
||||
File path: {provisionedId}
|
||||
</p>
|
||||
<HorizontalGroup justify="center">
|
||||
<Button variant="secondary" onClick={hideModal}>
|
||||
OK
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</Modal>
|
||||
);
|
@ -0,0 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { updateLocation } from 'app/core/reducers/location';
|
||||
import { deleteDashboard } from 'app/features/manage-dashboards/state/actions';
|
||||
|
||||
export const useDashboardDelete = (uid: string) => {
|
||||
const dispatch = useDispatch();
|
||||
const [state, onRestoreDashboard] = useAsyncFn(() => deleteDashboard(uid, false), []);
|
||||
useEffect(() => {
|
||||
if (state.value) {
|
||||
dispatch(
|
||||
updateLocation({
|
||||
path: '/',
|
||||
replace: true,
|
||||
query: {},
|
||||
})
|
||||
);
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', state.value.title + ' has been deleted']);
|
||||
}
|
||||
}, [state]);
|
||||
return { state, onRestoreDashboard };
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { HorizontalGroup, Modal, Button } from '@grafana/ui';
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
import { useDashboardRestore } from './useDashboardRestore';
|
||||
export interface RevertDashboardModalProps {
|
||||
hideModal: () => void;
|
||||
@ -12,25 +11,16 @@ export const RevertDashboardModal: React.FC<RevertDashboardModalProps> = ({ hide
|
||||
const { onRestoreDashboard } = useDashboardRestore(version);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<ConfirmModal
|
||||
isOpen={true}
|
||||
title="Restore Version"
|
||||
icon="history"
|
||||
onDismiss={hideModal}
|
||||
className={css`
|
||||
text-align: center;
|
||||
width: 500px;
|
||||
`}
|
||||
>
|
||||
<p>Are you sure you want to restore the dashboard to version {version}? All unsaved changes will be lost.</p>
|
||||
<HorizontalGroup justify="center">
|
||||
<Button variant="destructive" type="button" onClick={onRestoreDashboard}>
|
||||
Yes, restore to version {version}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={hideModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</Modal>
|
||||
onConfirm={onRestoreDashboard}
|
||||
body={
|
||||
<p>Are you sure you want to restore the dashboard to version {version}? All unsaved changes will be lost.</p>
|
||||
}
|
||||
confirmText={`Yes, restore to version ${version}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ import { DashboardInitPhase, DashboardRouteInfo } from 'app/types';
|
||||
import { notifyApp, updateLocation } from 'app/core/actions';
|
||||
import { cleanUpDashboardAndVariables } from '../state/actions';
|
||||
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({}));
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => ({}));
|
||||
|
||||
interface ScenarioContext {
|
||||
cleanUpDashboardAndVariablesMock: typeof cleanUpDashboardAndVariables;
|
||||
|
@ -5,7 +5,7 @@ import { Props as DashboardPanelProps } from '../dashgrid/DashboardPanel';
|
||||
import { DashboardModel } from '../state';
|
||||
import { DashboardRouteInfo } from 'app/types';
|
||||
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({}));
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => ({}));
|
||||
jest.mock('app/features/dashboard/dashgrid/DashboardPanel', () => {
|
||||
class DashboardPanel extends React.Component<DashboardPanelProps> {
|
||||
render() {
|
||||
|
@ -8,9 +8,3 @@ import './components/DashExportModal';
|
||||
import './components/DashNav';
|
||||
import './components/VersionHistory';
|
||||
import './components/DashboardSettings';
|
||||
|
||||
import { DashboardPermissions } from './components/DashboardPermissions/DashboardPermissions';
|
||||
// angular wrappers
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
|
||||
react2AngularDirective('dashboardPermissions', DashboardPermissions, ['dashboardId', 'folder']);
|
||||
|
Loading…
Reference in New Issue
Block a user