import React, { PureComponent } from 'react'; import { isEmptyObject, SelectableValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Button, ClipboardButton, Field, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { VariableRefresh } from '../../../variables/types'; import { trackDashboardSharingActionPerType } from './analytics'; import { ShareModalTabProps } from './types'; import { shareDashboardType } from './utils'; const snapshotApiUrl = '/api/snapshots'; interface Props extends ShareModalTabProps {} interface State { isLoading: boolean; step: number; snapshotName: string; selectedExpireOption: SelectableValue; snapshotExpires?: number; snapshotUrl: string; deleteUrl: string; timeoutSeconds: number; externalEnabled: boolean; sharingButtonText: string; } export class ShareSnapshot extends PureComponent { private dashboard: DashboardModel; private expireOptions: Array>; constructor(props: Props) { super(props); this.dashboard = props.dashboard; this.expireOptions = [ { label: t('share-modal.snapshot.expire-never', `Never`), value: 0, }, { label: t('share-modal.snapshot.expire-hour', `1 Hour`), value: 60 * 60, }, { label: t('share-modal.snapshot.expire-day', `1 Day`), value: 60 * 60 * 24, }, { label: t('share-modal.snapshot.expire-week', `7 Days`), value: 60 * 60 * 24 * 7, }, ]; this.state = { isLoading: false, step: 1, selectedExpireOption: this.expireOptions[0], snapshotExpires: this.expireOptions[0].value, snapshotName: props.dashboard.title, timeoutSeconds: 4, snapshotUrl: '', deleteUrl: '', externalEnabled: false, sharingButtonText: '', }; } componentDidMount() { this.getSnaphotShareOptions(); } async getSnaphotShareOptions() { const shareOptions = await getBackendSrv().get('/api/snapshot/shared-options'); this.setState({ sharingButtonText: shareOptions['externalSnapshotName'], externalEnabled: shareOptions['externalEnabled'], }); } createSnapshot = (external?: boolean) => () => { const { timeoutSeconds } = this.state; this.dashboard.snapshot = { timestamp: new Date(), }; this.setState({ isLoading: true }); this.dashboard.startRefresh(); setTimeout(() => { this.saveSnapshot(this.dashboard, external); }, timeoutSeconds * 1000); }; saveSnapshot = async (dashboard: DashboardModel, external?: boolean) => { const { snapshotExpires } = this.state; const dash = this.dashboard.getSaveModelClone(); this.scrubDashboard(dash); const cmdData = { dashboard: dash, name: dash.title, expires: snapshotExpires, external: external, }; try { const results: { deleteUrl: string; url: string } = await getBackendSrv().post(snapshotApiUrl, cmdData); this.setState({ deleteUrl: results.deleteUrl, snapshotUrl: results.url, step: 2, }); } finally { trackDashboardSharingActionPerType(external ? 'publish_snapshot' : 'local_snapshot', shareDashboardType.snapshot); this.setState({ isLoading: false }); } }; scrubDashboard = (dash: DashboardModel) => { const { panel } = this.props; const { snapshotName } = this.state; // change title dash.title = snapshotName; // make relative times absolute dash.time = getTimeSrv().timeRange(); // Remove links dash.links = []; // remove panel queries & links dash.panels.forEach((panel) => { panel.targets = []; panel.links = []; panel.datasource = null; }); // remove annotation queries const annotations = dash.annotations.list.filter((annotation) => annotation.enable); dash.annotations.list = annotations.map((annotation) => { return { name: annotation.name, enable: annotation.enable, iconColor: annotation.iconColor, snapshotData: annotation.snapshotData, type: annotation.type, builtIn: annotation.builtIn, hide: annotation.hide, }; }); // remove template queries dash.getVariables().forEach((variable) => { if ('query' in variable) { variable.query = ''; } if ('options' in variable) { variable.options = variable.current && !isEmptyObject(variable.current) ? [variable.current] : []; } if ('refresh' in variable) { variable.refresh = VariableRefresh.never; } }); // snapshot single panel if (panel) { const singlePanel = panel.getSaveModel(); singlePanel.gridPos.w = 24; singlePanel.gridPos.x = 0; singlePanel.gridPos.y = 0; singlePanel.gridPos.h = 20; dash.panels = [singlePanel]; } // cleanup snapshotData delete this.dashboard.snapshot; this.dashboard.forEachPanel((panel: PanelModel) => { delete panel.snapshotData; }); this.dashboard.annotations.list.forEach((annotation) => { delete annotation.snapshotData; }); }; deleteSnapshot = async () => { const { deleteUrl } = this.state; await getBackendSrv().get(deleteUrl); this.setState({ step: 3 }); }; getSnapshotUrl = () => { return this.state.snapshotUrl; }; onSnapshotNameChange = (event: React.ChangeEvent) => { this.setState({ snapshotName: event.target.value }); }; onTimeoutChange = (event: React.ChangeEvent) => { this.setState({ timeoutSeconds: Number(event.target.value) }); }; onExpireChange = (option: SelectableValue) => { this.setState({ selectedExpireOption: option, snapshotExpires: option.value, }); }; renderStep1() { const { onDismiss } = this.props; const { snapshotName, selectedExpireOption, timeoutSeconds, isLoading, sharingButtonText, externalEnabled } = this.state; const snapshotNameTranslation = t('share-modal.snapshot.name', `Snapshot name`); const expireTranslation = t('share-modal.snapshot.expire', `Expire`); const timeoutTranslation = t('share-modal.snapshot.timeout', `Timeout (seconds)`); const timeoutDescriptionTranslation = t( 'share-modal.snapshot.timeout-description', `You might need to configure the timeout value if it takes a long time to collect your dashboard metrics.` ); return ( <>

A snapshot is an instant way to share an interactive dashboard publicly. When created, we strip sensitive data like queries (metric, template, and annotation) and panel links, leaving only the visible metric data and series names embedded in your dashboard.

Keep in mind, your snapshot can be viewed by anyone that has the link and can access the URL. Share wisely.

{externalEnabled && ( )} ); } renderStep2() { const { snapshotUrl } = this.state; return ( <> Copy } />
Did you make a mistake?   Delete snapshot.
); } renderStep3() { return (

The snapshot has been deleted. If you have already accessed it once, then it might take up to an hour before before it is removed from browser caches or CDN caches.

); } render() { const { isLoading, step } = this.state; return ( <> {step === 1 && this.renderStep1()} {step === 2 && this.renderStep2()} {step === 3 && this.renderStep3()} {isLoading && } ); } }