diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less
index 34ae3a4984..d6a387cefb 100644
--- a/client/app/assets/less/ant.less
+++ b/client/app/assets/less/ant.less
@@ -18,6 +18,7 @@
@import '~antd/lib/icon/style/index';
@import '~antd/lib/tag/style/index';
@import '~antd/lib/grid/style/index';
+@import '~antd/lib/switch/style/index';
@import 'inc/ant-variables';
// Remove bold in labels for Ant checkboxes and radio buttons
@@ -195,4 +196,12 @@
line-height: 46px;
}
}
+}
+
+// description in modal header
+.modal-header-desc {
+ font-size: @font-size-base;
+ color: @text-color-secondary;
+ font-weight: normal;
+ margin-top: 4px;
}
\ No newline at end of file
diff --git a/client/app/components/InputWithCopy.jsx b/client/app/components/InputWithCopy.jsx
new file mode 100644
index 0000000000..c2e4571aaa
--- /dev/null
+++ b/client/app/components/InputWithCopy.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import Input from 'antd/lib/input';
+import Icon from 'antd/lib/icon';
+import Tooltip from 'antd/lib/tooltip';
+
+export default class InputWithCopy extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { copied: null };
+ this.ref = React.createRef();
+ this.copyFeatureSupported = document.queryCommandSupported('copy');
+ this.resetCopyState = null;
+ }
+
+ componentWillUnmount() {
+ if (this.resetCopyState) {
+ clearTimeout(this.resetCopyState);
+ }
+ }
+
+ copy = () => {
+ // select text
+ this.ref.current.select();
+
+ // copy
+ try {
+ const success = document.execCommand('copy');
+ if (!success) {
+ throw new Error();
+ }
+ this.setState({ copied: 'Copied!' });
+ } catch (err) {
+ this.setState({
+ copied: 'Copy failed',
+ });
+ }
+
+ // reset tooltip
+ this.resetCopyState = setTimeout(() => this.setState({ copied: null }), 2000);
+ };
+
+ render() {
+ const copyButton = (
+
+
+
+ );
+
+ return (
+
+ );
+ }
+}
diff --git a/client/app/pages/dashboards/ShareDashboardDialog.jsx b/client/app/pages/dashboards/ShareDashboardDialog.jsx
new file mode 100644
index 0000000000..5d8569d58c
--- /dev/null
+++ b/client/app/pages/dashboards/ShareDashboardDialog.jsx
@@ -0,0 +1,122 @@
+import { replace } from 'lodash';
+import React from 'react';
+import PropTypes from 'prop-types';
+import Switch from 'antd/lib/switch';
+import Modal from 'antd/lib/modal';
+import Form from 'antd/lib/form';
+import Tooltip from 'antd/lib/tooltip';
+import { $http, toastr } from '@/services/ng';
+import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper';
+import InputWithCopy from '@/components/InputWithCopy';
+
+const API_SHARE_URL = 'api/dashboards/{id}/share';
+const HELP_URL = 'https://redash.io/help/user-guide/dashboards/sharing-dashboards?source=dialog';
+
+class ShareDashboardDialog extends React.Component {
+ static propTypes = {
+ dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+ dialog: DialogPropType.isRequired,
+ };
+
+ formItemProps = {
+ labelCol: { span: 8 },
+ wrapperCol: { span: 16 },
+ style: { marginBottom: 7 },
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ saving: false,
+ };
+ this.apiUrl = replace(API_SHARE_URL, '{id}', props.dashboard.id);
+ }
+
+ static get headerContent() {
+ return (
+
+ Share Dashboard
+
+ Allow public access to this dashboard with a secret address.{' '}
+
+ { /* eslint-disable-next-line react/jsx-no-target-blank */}
+ Learn more
+
+
+
+ );
+ }
+
+ enable = () => {
+ const { dashboard } = this.props;
+ this.setState({ saving: true });
+
+ $http
+ .post(this.apiUrl)
+ .success((data) => {
+ dashboard.publicAccessEnabled = true;
+ dashboard.public_url = data.public_url;
+ })
+ .error(() => {
+ toastr.error('Failed to turn on sharing for this dashboard');
+ })
+ .finally(() => {
+ this.setState({ saving: false });
+ });
+ }
+
+ disable = () => {
+ const { dashboard } = this.props;
+ this.setState({ saving: true });
+
+ $http
+ .delete(this.apiUrl)
+ .success(() => {
+ dashboard.publicAccessEnabled = false;
+ delete dashboard.public_url;
+ })
+ .error(() => {
+ toastr.error('Failed to turn off sharing for this dashboard');
+ })
+ .finally(() => {
+ this.setState({ saving: false });
+ });
+ }
+
+ onChange = (checked) => {
+ if (checked) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ };
+
+ render() {
+ const { dialog, dashboard } = this.props;
+
+ return (
+
+
+
+
+ {dashboard.public_url && (
+
+
+
+ )}
+
+
+ );
+ }
+}
+
+export default wrapDialog(ShareDashboardDialog);
diff --git a/client/app/pages/dashboards/dashboard.js b/client/app/pages/dashboards/dashboard.js
index aa89682812..4ebbf2ebe7 100644
--- a/client/app/pages/dashboards/dashboard.js
+++ b/client/app/pages/dashboards/dashboard.js
@@ -4,7 +4,7 @@ import getTags from '@/services/getTags';
import { policy } from '@/services/policy';
import { durationHumanize } from '@/filters';
import template from './dashboard.html';
-import shareDashboardTemplate from './share-dashboard.html';
+import ShareDashboardDialog from './ShareDashboardDialog';
import AddWidgetDialog from '@/components/dashboards/AddWidgetDialog';
import './dashboard.less';
@@ -392,60 +392,11 @@ function DashboardCtrl(
}
this.openShareForm = () => {
- $uibModal.open({
- component: 'shareDashboard',
- resolve: {
- dashboard: this.dashboard,
- },
- });
+ ShareDashboardDialog.showModal({ dashboard: this.dashboard });
};
}
-const ShareDashboardComponent = {
- template: shareDashboardTemplate,
- bindings: {
- resolve: '<',
- close: '&',
- dismiss: '&',
- },
- controller($http) {
- 'ngInject';
-
- this.dashboard = this.resolve.dashboard;
-
- this.toggleSharing = () => {
- const url = `api/dashboards/${this.dashboard.id}/share`;
-
- if (!this.dashboard.publicAccessEnabled) {
- // disable
- $http
- .delete(url)
- .success(() => {
- this.dashboard.publicAccessEnabled = false;
- delete this.dashboard.public_url;
- })
- .error(() => {
- this.dashboard.publicAccessEnabled = true;
- // TODO: show message
- });
- } else {
- $http
- .post(url)
- .success((data) => {
- this.dashboard.publicAccessEnabled = true;
- this.dashboard.public_url = data.public_url;
- })
- .error(() => {
- this.dashboard.publicAccessEnabled = false;
- // TODO: show message
- });
- }
- };
- },
-};
-
export default function init(ngModule) {
- ngModule.component('shareDashboard', ShareDashboardComponent);
ngModule.component('dashboardPage', {
template,
controller: DashboardCtrl,
diff --git a/client/app/pages/dashboards/share-dashboard.html b/client/app/pages/dashboards/share-dashboard.html
deleted file mode 100644
index 14400de462..0000000000
--- a/client/app/pages/dashboards/share-dashboard.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-