mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Migrating dashboard settings to react (#27561)
* Dashboard: Started migrating dashboard settings * Restore general settings from angular * Use react permissions component * feat(dashboard): add react LinksSettings wrapper for dash-links-editor * feat(dashboard): add react VersionsSettings wrapper for gf-dashboard-history * refactor(dashboard): replace DashboardPermissions connectWithStore with connect * chore(dashboard): folderInfo as undefined * feat(dashboard): initial commit of dashboard settings json editor * feat(dashboard): introduce save json functionality * chore(dashboard): delete obsolete imports * feat(dashboard): add save and save as buttons to settings nav * feat(dashboard): add react wrapper for annotations settings * chore(dashboard): put back canDelete for general settings delete button * Make editable * Remove makeEditable from SettingsCtrl * feat(dashboard): show json editor save button if canSave * refactor(dashboard): move hasUnsavedFolderChange to dashboard.meta * feat(dashboard): render hasUnsavedFolderChange view in permissions settings * feat(dashboard): reset hasUnsavedFolderChange on settingg save success * feat(dashboard): refresh route on sucessful settings save * test(dashboard): update snapshots * refactor(dashboard): automatically infer connected props for dashboard permissions * refactor(dashboard): give dashboard versions checkboxes some padding * Update public/app/types/folders.ts Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
This commit is contained in:
parent
0561c941af
commit
d066da42f8
@ -111,6 +111,7 @@ export type IconName =
|
||||
| 'search-plus'
|
||||
| 'minus-circle'
|
||||
| 'table'
|
||||
| 'arrow'
|
||||
| 'plus'
|
||||
| 'heart'
|
||||
| 'heart-break'
|
||||
@ -229,6 +230,7 @@ export const getAvailableIcons = (): IconName[] => [
|
||||
'signal',
|
||||
'search-plus',
|
||||
'minus-circle',
|
||||
'arrow',
|
||||
'table',
|
||||
'plus',
|
||||
'heart',
|
||||
|
@ -139,7 +139,6 @@ export const getStandardFieldConfigs = () => {
|
||||
id: 'thresholds',
|
||||
path: 'thresholds',
|
||||
name: 'Thresholds',
|
||||
|
||||
editor: standardEditorsRegistry.get('thresholds').editor as any,
|
||||
override: standardEditorsRegistry.get('thresholds').editor as any,
|
||||
process: thresholdsOverrideProcessor,
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
SaveDashboardAsButtonConnected,
|
||||
SaveDashboardButtonConnected,
|
||||
} from '../features/dashboard/components/SaveDashboard/SaveDashboardButton';
|
||||
import { VariableEditorContainer } from '../features/variables/editor/VariableEditorContainer';
|
||||
import { SearchField, SearchResults, SearchResultsFilter, SearchWrapper } from '../features/search';
|
||||
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
|
||||
|
||||
@ -200,7 +199,6 @@ export function registerAngularDirectives() {
|
||||
['getDashboard', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onSaveSuccess', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('variableEditorContainer', VariableEditorContainer, []);
|
||||
react2AngularDirective('timePickerSettings', TimePickerSettings, [
|
||||
'renderCount',
|
||||
'refreshIntervals',
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Tooltip, Icon } from '@grafana/ui';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { Tooltip, Icon, Button } from '@grafana/ui';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
import { StoreState, FolderInfo } from 'app/types';
|
||||
import { StoreState } from 'app/types';
|
||||
import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
|
||||
import {
|
||||
getDashboardPermissions,
|
||||
@ -9,90 +10,10 @@ import {
|
||||
removeDashboardPermission,
|
||||
updateDashboardPermission,
|
||||
} from '../../state/actions';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import PermissionList from 'app/core/components/PermissionList/PermissionList';
|
||||
import AddPermission from 'app/core/components/PermissionList/AddPermission';
|
||||
import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo';
|
||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||
|
||||
export interface Props {
|
||||
dashboardId: number;
|
||||
folder?: FolderInfo;
|
||||
permissions: DashboardAcl[];
|
||||
getDashboardPermissions: typeof getDashboardPermissions;
|
||||
updateDashboardPermission: typeof updateDashboardPermission;
|
||||
removeDashboardPermission: typeof removeDashboardPermission;
|
||||
addDashboardPermission: typeof addDashboardPermission;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isAdding: boolean;
|
||||
}
|
||||
|
||||
export class DashboardPermissions extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isAdding: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getDashboardPermissions(this.props.dashboardId);
|
||||
}
|
||||
|
||||
onOpenAddPermissions = () => {
|
||||
this.setState({ isAdding: true });
|
||||
};
|
||||
|
||||
onRemoveItem = (item: DashboardAcl) => {
|
||||
this.props.removeDashboardPermission(this.props.dashboardId, item);
|
||||
};
|
||||
|
||||
onPermissionChanged = (item: DashboardAcl, level: PermissionLevel) => {
|
||||
this.props.updateDashboardPermission(this.props.dashboardId, item, level);
|
||||
};
|
||||
|
||||
onAddPermission = (newItem: NewDashboardAclItem) => {
|
||||
return this.props.addDashboardPermission(this.props.dashboardId, newItem);
|
||||
};
|
||||
|
||||
onCancelAddPermission = () => {
|
||||
this.setState({ isAdding: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { permissions, folder } = this.props;
|
||||
const { isAdding } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="dashboard-settings__header">
|
||||
<div className="page-action-bar">
|
||||
<h3 className="d-inline-block">Permissions</h3>
|
||||
<Tooltip placement="auto" content={<PermissionsInfo />}>
|
||||
<Icon className="icon--has-hover page-sub-heading-icon" name="question-circle" />
|
||||
</Tooltip>
|
||||
<div className="page-action-bar__spacer" />
|
||||
<button className="btn btn-primary pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
|
||||
Add Permission
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<SlideDown in={isAdding}>
|
||||
<AddPermission onAddPermission={this.onAddPermission} onCancel={this.onCancelAddPermission} />
|
||||
</SlideDown>
|
||||
<PermissionList
|
||||
items={permissions}
|
||||
onRemoveItem={this.onRemoveItem}
|
||||
onPermissionChanged={this.onPermissionChanged}
|
||||
isFetching={false}
|
||||
folderInfo={folder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
permissions: state.dashboard.permissions,
|
||||
@ -105,4 +26,99 @@ const mapDispatchToProps = {
|
||||
updateDashboardPermission,
|
||||
};
|
||||
|
||||
export default connectWithStore(DashboardPermissions, mapStateToProps, mapDispatchToProps);
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
|
||||
export interface State {
|
||||
isAdding: boolean;
|
||||
}
|
||||
|
||||
export class DashboardPermissionsUnconnected extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isAdding: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getDashboardPermissions(this.props.dashboard.id);
|
||||
}
|
||||
|
||||
onOpenAddPermissions = () => {
|
||||
this.setState({ isAdding: true });
|
||||
};
|
||||
|
||||
onRemoveItem = (item: DashboardAcl) => {
|
||||
this.props.removeDashboardPermission(this.props.dashboard.id, item);
|
||||
};
|
||||
|
||||
onPermissionChanged = (item: DashboardAcl, level: PermissionLevel) => {
|
||||
this.props.updateDashboardPermission(this.props.dashboard.id, item, level);
|
||||
};
|
||||
|
||||
onAddPermission = (newItem: NewDashboardAclItem) => {
|
||||
return this.props.addDashboardPermission(this.props.dashboard.id, newItem);
|
||||
};
|
||||
|
||||
onCancelAddPermission = () => {
|
||||
this.setState({ isAdding: false });
|
||||
};
|
||||
|
||||
getFolder() {
|
||||
const { dashboard } = this.props;
|
||||
|
||||
return {
|
||||
id: dashboard.meta.folderId,
|
||||
title: dashboard.meta.folderTitle,
|
||||
url: dashboard.meta.folderUrl,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
permissions,
|
||||
dashboard: {
|
||||
meta: { hasUnsavedFolderChange },
|
||||
},
|
||||
} = this.props;
|
||||
const { isAdding } = this.state;
|
||||
|
||||
return hasUnsavedFolderChange ? (
|
||||
<h5>You have changed folder, please save to view permissions.</h5>
|
||||
) : (
|
||||
<div>
|
||||
<div className="dashboard-settings__header">
|
||||
<div className="page-action-bar">
|
||||
<h3 className="d-inline-block">Permissions</h3>
|
||||
<Tooltip placement="auto" content={<PermissionsInfo />}>
|
||||
<Icon className="icon--has-hover page-sub-heading-icon" name="question-circle" />
|
||||
</Tooltip>
|
||||
<div className="page-action-bar__spacer" />
|
||||
<Button className="pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
|
||||
Add Permission
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<SlideDown in={isAdding}>
|
||||
<AddPermission onAddPermission={this.onAddPermission} onCancel={this.onCancelAddPermission} />
|
||||
</SlideDown>
|
||||
<PermissionList
|
||||
items={permissions}
|
||||
onRemoveItem={this.onRemoveItem}
|
||||
onPermissionChanged={this.onPermissionChanged}
|
||||
isFetching={false}
|
||||
folderInfo={this.getFolder()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const DashboardPermissions = connector(DashboardPermissionsUnconnected);
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class AnnotationsSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp?: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<div ng-include="\'public/app/features/annotations/partials/editor.html\'" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={ref => (this.element = ref)} />;
|
||||
}
|
||||
}
|
@ -1,39 +1,27 @@
|
||||
// Libaries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Utils & Services
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { cx } from 'emotion';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Button, CustomScrollbar, Icon, IconName } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { SaveDashboardButton, SaveDashboardAsButton } from '../SaveDashboard/SaveDashboardButton';
|
||||
import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer';
|
||||
import { DashboardPermissions } from '../DashboardPermissions/DashboardPermissions';
|
||||
import { GeneralSettings } from './GeneralSettings';
|
||||
import { AnnotationsSettings } from './AnnotationsSettings';
|
||||
import { LinksSettings } from './LinksSettings';
|
||||
import { VersionsSettings } from './VersionsSettings';
|
||||
import { JsonEditorSettings } from './JsonEditorSettings';
|
||||
export interface Props {
|
||||
dashboard: DashboardModel;
|
||||
updateLocation: typeof updateLocation;
|
||||
editview: string;
|
||||
}
|
||||
|
||||
export class DashboardSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<dashboard-settings dashboard="dashboard" class="dashboard-settings__body2" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.updateLocation({
|
||||
query: { editview: null },
|
||||
@ -41,10 +29,119 @@ export class DashboardSettings extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
onChangePage = (id: string) => {
|
||||
this.props.updateLocation({
|
||||
query: { editview: id },
|
||||
partial: true,
|
||||
});
|
||||
};
|
||||
|
||||
getPages(): SettingsPage[] {
|
||||
const { dashboard } = this.props;
|
||||
const pages: SettingsPage[] = [];
|
||||
|
||||
if (dashboard.meta.canEdit) {
|
||||
pages.push(this.getGeneralPage());
|
||||
|
||||
pages.push({
|
||||
title: 'Annotations',
|
||||
id: 'annotations',
|
||||
icon: 'comment-alt',
|
||||
render: () => <AnnotationsSettings dashboard={dashboard} />,
|
||||
});
|
||||
|
||||
pages.push({
|
||||
title: 'Variables',
|
||||
id: 'templating',
|
||||
icon: 'calculator-alt',
|
||||
render: () => <VariableEditorContainer />,
|
||||
});
|
||||
|
||||
pages.push({
|
||||
title: 'Links',
|
||||
id: 'links',
|
||||
icon: 'link',
|
||||
render: () => <LinksSettings dashboard={dashboard} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.meta.canMakeEditable) {
|
||||
pages.push({
|
||||
title: 'General',
|
||||
icon: 'sliders-v-alt',
|
||||
id: 'settings',
|
||||
render: () => this.renderMakeEditable(),
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.id && dashboard.meta.canSave) {
|
||||
pages.push({
|
||||
title: 'Versions',
|
||||
id: 'versions',
|
||||
icon: 'history',
|
||||
render: () => <VersionsSettings dashboard={dashboard} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.id && dashboard.meta.canAdmin) {
|
||||
pages.push({
|
||||
title: 'Permissions',
|
||||
id: 'permissions',
|
||||
icon: 'lock',
|
||||
render: () => <DashboardPermissions dashboard={dashboard} />,
|
||||
});
|
||||
}
|
||||
|
||||
pages.push({
|
||||
title: 'JSON Model',
|
||||
id: 'dashboard_json',
|
||||
icon: 'arrow',
|
||||
render: () => <JsonEditorSettings dashboard={dashboard} />,
|
||||
});
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
onMakeEditable = () => {
|
||||
const { dashboard } = this.props;
|
||||
dashboard.editable = true;
|
||||
dashboard.meta.canMakeEditable = false;
|
||||
dashboard.meta.canEdit = true;
|
||||
dashboard.meta.canSave = true;
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onPostSave = () => {
|
||||
this.props.dashboard.meta.hasUnsavedFolderChange = false;
|
||||
dashboardWatcher.reloadPage();
|
||||
};
|
||||
|
||||
renderMakeEditable(): React.ReactNode {
|
||||
return (
|
||||
<div>
|
||||
<div className="dashboard-settings__header">Dashboard not editable</div>
|
||||
<Button onClick={this.onMakeEditable}>Make editable</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getGeneralPage(): SettingsPage {
|
||||
return {
|
||||
title: 'General',
|
||||
id: 'settings',
|
||||
icon: 'sliders-v-alt',
|
||||
render: () => <GeneralSettings dashboard={this.props.dashboard} />,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, editview } = this.props;
|
||||
const folderTitle = dashboard.meta.folderTitle;
|
||||
const haveFolder = (dashboard.meta.folderId ?? 0) > 0;
|
||||
const pages = this.getPages();
|
||||
const currentPage = pages.find(page => page.id === editview) ?? pages[0];
|
||||
const canSaveAs = contextSrv.hasEditPermissionInFolders;
|
||||
const canSave = dashboard.meta.canSave;
|
||||
|
||||
return (
|
||||
<div className="dashboard-settings">
|
||||
@ -58,9 +155,37 @@ export class DashboardSettings extends PureComponent<Props> {
|
||||
</div>
|
||||
</div>
|
||||
<CustomScrollbar>
|
||||
<div className="dashboard-settings__body1" ref={element => (this.element = element)} />
|
||||
<div className="dashboard-settings__body">
|
||||
<aside className="dashboard-settings__aside">
|
||||
{pages.map(page => (
|
||||
<a
|
||||
className={cx('dashboard-settings__nav-item', { active: page.id === editview })}
|
||||
aria-label={selectors.pages.Dashboard.Settings.General.sectionItems(page.title)}
|
||||
onClick={() => this.onChangePage(page.id)}
|
||||
key={page.id}
|
||||
>
|
||||
<Icon name={page.icon} style={{ marginRight: '4px' }} />
|
||||
{page.title}
|
||||
</a>
|
||||
))}
|
||||
<div className="dashboard-settings__aside-actions">
|
||||
{canSave && <SaveDashboardButton dashboard={dashboard} onSaveSuccess={this.onPostSave} />}
|
||||
{canSaveAs && (
|
||||
<SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={this.onPostSave} variant="secondary" />
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
<div className="dashboard-settings__content">{currentPage.render()}</div>
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface SettingsPage {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: IconName;
|
||||
render: () => React.ReactNode;
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class GeneralSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp?: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<dashboard-settings dashboard="dashboard" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={ref => (this.element = ref)} />;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import React, { useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Button, CodeEditor } from '@grafana/ui';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { getDashboardSrv } from '../../services/DashboardSrv';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export const JsonEditorSettings: React.FC<Props> = ({ dashboard }) => {
|
||||
const [dashboardJson, setDashboardJson] = useState<string>(JSON.stringify(dashboard.getSaveModelClone(), null, 2));
|
||||
const onBlur = (value: string) => {
|
||||
setDashboardJson(value);
|
||||
};
|
||||
const onClick = () => {
|
||||
getDashboardSrv()
|
||||
.saveJSONDashboard(dashboardJson)
|
||||
.then(() => {
|
||||
dashboardWatcher.reloadPage();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="dashboard-settings__header">JSON Model</h3>
|
||||
<div className="dashboard-settings__subheader">
|
||||
The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout,
|
||||
queries etc.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<CodeEditor
|
||||
value={dashboardJson}
|
||||
language="json"
|
||||
width={width}
|
||||
height="500px"
|
||||
showMiniMap={false}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
{dashboard.meta.canSave && (
|
||||
<Button className="m-t-3" onClick={onClick}>
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class LinksSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp?: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<dash-links-editor dashboard="dashboard" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={ref => (this.element = ref)} />;
|
||||
}
|
||||
}
|
@ -1,183 +1,32 @@
|
||||
import _ from 'lodash';
|
||||
import angular, { ILocationService, IScope } from 'angular';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { appEvents, contextSrv, coreModule } from 'app/core/core';
|
||||
import { appEvents, coreModule } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { DashboardSrv } from '../../services/DashboardSrv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { AppEvents, locationUtil, TimeZone, urlUtil } from '@grafana/data';
|
||||
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;
|
||||
isOpen: boolean;
|
||||
viewId: string;
|
||||
json: string;
|
||||
alertCount: number;
|
||||
canSaveAs: boolean;
|
||||
canSave?: boolean;
|
||||
canDelete?: boolean;
|
||||
sections: any[];
|
||||
hasUnsavedFolderChange: 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 $route: any,
|
||||
private $location: ILocationService,
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private dashboardSrv: DashboardSrv
|
||||
) {
|
||||
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.$scope.$on('$destroy', () => {
|
||||
this.dashboard.updateSubmenuVisibility();
|
||||
|
||||
setTimeout(() => {
|
||||
this.dashboard.startRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
this.canSaveAs = contextSrv.hasEditPermissionInFolders;
|
||||
this.canSave = this.dashboard.meta.canSave;
|
||||
this.canDelete = this.dashboard.meta.canSave;
|
||||
|
||||
this.buildSectionList();
|
||||
this.onRouteUpdated();
|
||||
|
||||
this.$rootScope.onAppEvent(CoreEvents.routeUpdated, this.onRouteUpdated.bind(this), $scope);
|
||||
|
||||
appEvents.on(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope);
|
||||
|
||||
this.selectors = selectors.pages.Dashboard.Settings.General;
|
||||
this.renderCount = 0;
|
||||
}
|
||||
|
||||
buildSectionList() {
|
||||
this.sections = [];
|
||||
|
||||
if (this.dashboard.meta.canEdit) {
|
||||
this.sections.push({
|
||||
title: 'General',
|
||||
id: 'settings',
|
||||
icon: 'sliders-v-alt',
|
||||
});
|
||||
this.sections.push({
|
||||
title: 'Annotations',
|
||||
id: 'annotations',
|
||||
icon: 'comment-alt',
|
||||
});
|
||||
this.sections.push({
|
||||
title: 'Variables',
|
||||
id: 'templating',
|
||||
icon: 'calculator-alt',
|
||||
});
|
||||
this.sections.push({
|
||||
title: 'Links',
|
||||
id: 'links',
|
||||
icon: 'link',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.dashboard.id && this.dashboard.meta.canSave) {
|
||||
this.sections.push({
|
||||
title: 'Versions',
|
||||
id: 'versions',
|
||||
icon: 'history',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.dashboard.id && this.dashboard.meta.canAdmin) {
|
||||
this.sections.push({
|
||||
title: 'Permissions',
|
||||
id: 'permissions',
|
||||
icon: 'lock',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.dashboard.meta.canMakeEditable) {
|
||||
this.sections.push({
|
||||
title: 'General',
|
||||
icon: 'sliders-v-alt',
|
||||
id: 'make_editable',
|
||||
});
|
||||
}
|
||||
|
||||
this.sections.push({
|
||||
title: 'JSON Model',
|
||||
id: 'dashboard_json',
|
||||
icon: 'arrow',
|
||||
});
|
||||
|
||||
const params = this.$location.search();
|
||||
const url = this.$location.path();
|
||||
|
||||
for (const section of this.sections) {
|
||||
section.url = locationUtil.assureBaseUrl(urlUtil.renderUrl(url, { ...params, editview: section.id }));
|
||||
}
|
||||
}
|
||||
|
||||
onRouteUpdated() {
|
||||
this.viewId = this.$location.search().editview;
|
||||
|
||||
if (this.viewId) {
|
||||
this.json = angular.toJson(this.dashboard.getSaveModelClone(), true);
|
||||
}
|
||||
|
||||
if (this.viewId === 'settings' && this.dashboard.meta.canMakeEditable) {
|
||||
this.viewId = 'make_editable';
|
||||
}
|
||||
|
||||
const currentSection: any = _.find(this.sections, { id: this.viewId } as any);
|
||||
if (!currentSection) {
|
||||
this.sections.unshift({
|
||||
title: 'Not found',
|
||||
id: '404',
|
||||
icon: 'exclamation-triangle',
|
||||
});
|
||||
this.viewId = '404';
|
||||
}
|
||||
}
|
||||
saveDashboardJson() {
|
||||
this.dashboardSrv.saveJSONDashboard(this.json).then(() => {
|
||||
this.$route.reload();
|
||||
});
|
||||
}
|
||||
|
||||
onPostSave() {
|
||||
this.hasUnsavedFolderChange = false;
|
||||
}
|
||||
|
||||
hideSettings() {
|
||||
const urlParams = this.$location.search();
|
||||
delete urlParams.editview;
|
||||
setTimeout(() => {
|
||||
this.$rootScope.$apply(() => {
|
||||
this.$location.search(urlParams);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
makeEditable() {
|
||||
this.dashboard.editable = true;
|
||||
this.dashboard.meta.canMakeEditable = false;
|
||||
this.dashboard.meta.canEdit = true;
|
||||
this.dashboard.meta.canSave = true;
|
||||
this.canDelete = true;
|
||||
this.viewId = 'settings';
|
||||
this.buildSectionList();
|
||||
|
||||
const currentSection: any = _.find(this.sections, { id: this.viewId } as any);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(currentSection.url));
|
||||
}
|
||||
|
||||
deleteDashboard() {
|
||||
let confirmText = '';
|
||||
let text2 = this.dashboard.title;
|
||||
@ -237,19 +86,7 @@ export class SettingsCtrl {
|
||||
onFolderChange = (folder: { id: number; title: string }) => {
|
||||
this.dashboard.meta.folderId = folder.id;
|
||||
this.dashboard.meta.folderTitle = folder.title;
|
||||
this.hasUnsavedFolderChange = true;
|
||||
};
|
||||
|
||||
getFolder() {
|
||||
return {
|
||||
id: this.dashboard.meta.folderId,
|
||||
title: this.dashboard.meta.folderTitle,
|
||||
url: this.dashboard.meta.folderUrl,
|
||||
};
|
||||
}
|
||||
|
||||
getDashboard = () => {
|
||||
return this.dashboard;
|
||||
this.dashboard.meta.hasUnsavedFolderChange = true;
|
||||
};
|
||||
|
||||
onRefreshIntervalChange = (intervals: string[]) => {
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class VersionsSettings extends PureComponent<Props> {
|
||||
element?: HTMLElement | null;
|
||||
angularCmp?: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<gf-dashboard-history dashboard="dashboard" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={ref => (this.element = ref)} />;
|
||||
}
|
||||
}
|
@ -1,143 +1,64 @@
|
||||
<aside class="dashboard-settings__aside">
|
||||
<a href="{{::section.url}}"
|
||||
class="dashboard-settings__nav-item"
|
||||
ng-class="{active: ctrl.viewId === section.id}"
|
||||
ng-repeat="section in ctrl.sections"
|
||||
aria-label="{{ctrl.selectors.sectionItems(section.title)}}">
|
||||
<icon name="'{{::section.icon}}'" style="margin-right: 4px;"></icon>
|
||||
{{::section.title}}
|
||||
</a>
|
||||
|
||||
<div class="dashboard-settings__aside-actions">
|
||||
<div ng-show="ctrl.canSave">
|
||||
<save-dashboard-button getDashboard="ctrl.getDashboard" />
|
||||
</div>
|
||||
<div ng-show="ctrl.canSaveAs">
|
||||
<save-dashboard-as-button getDashboard="ctrl.getDashboard" variant="'secondary'" />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h3 class="dashboard-settings__header" aria-label="{{::ctrl.selectors.title}}">
|
||||
General
|
||||
</h3>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'settings'">
|
||||
<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-group">
|
||||
<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 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>
|
||||
<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>
|
||||
<bootstrap-tagsinput ng-model="ctrl.dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
</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>
|
||||
<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="dashboard-settings__content" ng-if="ctrl.viewId === 'annotations'"
|
||||
ng-include="'public/app/features/annotations/partials/editor.html'">
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'templating'">
|
||||
<variable-editor-container />
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'links'">
|
||||
<dash-links-editor dashboard="ctrl.dashboard"></dash-links-editor>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'versions'">
|
||||
<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'dashboard_json'">
|
||||
<h3 class="dashboard-settings__header">JSON Model</h3>
|
||||
<div class="dashboard-settings__subheader">
|
||||
The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout,
|
||||
queries etc.
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<code-editor content="ctrl.json" data-mode="json" data-max-lines=30></code-editor>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-primary" ng-click="ctrl.saveDashboardJson()" ng-show="ctrl.canSave">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'">
|
||||
<dashboard-permissions ng-if="ctrl.dashboard && !ctrl.hasUnsavedFolderChange" dashboardId="ctrl.dashboard.id"
|
||||
backendSrv="ctrl.backendSrv" folder="ctrl.getFolder()" />
|
||||
<div ng-if="ctrl.hasUnsavedFolderChange">
|
||||
<h5>You have changed folder, please save to view permissions.</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'">
|
||||
<h3 class="dashboard-settings__header">Settings view not found</h3>
|
||||
|
||||
<div>
|
||||
<h5>The settings page could not be found or you do not have permission to access it</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'make_editable'">
|
||||
<h3 class="dashboard-settings__header">Make Editable</h3>
|
||||
|
||||
<button class="btn btn-primary" ng-click="ctrl.makeEditable()">
|
||||
Make Editable
|
||||
<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>
|
||||
|
@ -31,7 +31,6 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="revision in ctrl.revisions">
|
||||
<td
|
||||
class="filter-table__switch-cell"
|
||||
bs-tooltip="!revision.checked && ctrl.canCompare ? 'You can only compare 2 versions at a time' : ''"
|
||||
data-placement="right"
|
||||
>
|
||||
|
@ -337,7 +337,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
|
||||
{inspectPanel && <PanelInspector dashboard={dashboard} panel={inspectPanel} defaultTab={inspectTab} />}
|
||||
{editPanel && <PanelEditor dashboard={dashboard} sourcePanel={editPanel} />}
|
||||
{editview && <DashboardSettings dashboard={dashboard} updateLocation={updateLocation} />}
|
||||
{editview && <DashboardSettings dashboard={dashboard} updateLocation={updateLocation} editview={editview} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -169,6 +170,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -264,6 +266,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -414,6 +417,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -541,6 +545,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -636,6 +641,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -736,6 +742,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -792,6 +799,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
editview="settings"
|
||||
updateLocation={[MockFunction]}
|
||||
/>
|
||||
</div>
|
||||
|
@ -92,6 +92,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -314,6 +315,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -536,6 +538,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -758,6 +761,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"hasUnsavedFolderChange": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
|
@ -9,7 +9,7 @@ import './components/DashNav';
|
||||
import './components/VersionHistory';
|
||||
import './components/DashboardSettings';
|
||||
|
||||
import DashboardPermissions from './components/DashboardPermissions/DashboardPermissions';
|
||||
import { DashboardPermissions } from './components/DashboardPermissions/DashboardPermissions';
|
||||
// angular wrappers
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
|
||||
|
@ -170,6 +170,7 @@ export class DashboardModel {
|
||||
meta.canEdit = meta.canEdit !== false;
|
||||
meta.showSettings = meta.canEdit;
|
||||
meta.canMakeEditable = meta.canSave && !this.editable;
|
||||
meta.hasUnsavedFolderChange = false;
|
||||
|
||||
if (!this.editable) {
|
||||
meta.canEdit = false;
|
||||
|
@ -5,8 +5,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types';
|
||||
import { StoreState } from '../../../types';
|
||||
import { VariableEditorEditor } from './VariableEditorEditor';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
||||
import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
import { getEditorVariables } from '../state/selectors';
|
||||
import { VariableModel } from '../types';
|
||||
import { switchToEditMode, switchToListMode, switchToNewMode } from './actions';
|
||||
@ -140,8 +139,4 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
switchToListMode,
|
||||
};
|
||||
|
||||
export const VariableEditorContainer = connectWithStore(
|
||||
VariableEditorContainerUnconnected,
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
);
|
||||
export const VariableEditorContainer = connect(mapStateToProps, mapDispatchToProps)(VariableEditorContainerUnconnected);
|
||||
|
@ -34,6 +34,7 @@ export interface DashboardMeta {
|
||||
createdBy?: string;
|
||||
updated?: string;
|
||||
updatedBy?: string;
|
||||
hasUnsavedFolderChange?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardDataDTO {
|
||||
|
@ -23,7 +23,7 @@ export interface FolderState {
|
||||
}
|
||||
|
||||
export interface FolderInfo {
|
||||
id: number;
|
||||
title: string;
|
||||
url: string;
|
||||
id?: number;
|
||||
title?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
@ -12,12 +12,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-settings__body1 {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboard-settings__body2 {
|
||||
.dashboard-settings__body {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
Loading…
Reference in New Issue
Block a user