123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- import React, { PureComponent } from 'react';
- import { AppEvents, SelectableValue } from '@grafana/data';
- import { getBackendSrv, reportInteraction } from '@grafana/runtime';
- import { Button, ClipboardButton, Field, Icon, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui';
- import { appEvents } from 'app/core/core';
- import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
- import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
- import { VariableRefresh } from '../../../variables/types';
- import { ShareModalTabProps } from './types';
- const snapshotApiUrl = '/api/snapshots';
- const expireOptions: Array<SelectableValue<number>> = [
- { label: 'Never', value: 0 },
- { label: '1 Hour', value: 60 * 60 },
- { label: '1 Day', value: 60 * 60 * 24 },
- { label: '7 Days', value: 60 * 60 * 24 * 7 },
- ];
- interface Props extends ShareModalTabProps {}
- interface State {
- isLoading: boolean;
- step: number;
- snapshotName: string;
- selectedExpireOption: SelectableValue<number>;
- snapshotExpires?: number;
- snapshotUrl: string;
- deleteUrl: string;
- timeoutSeconds: number;
- externalEnabled: boolean;
- sharingButtonText: string;
- }
- export class ShareSnapshot extends PureComponent<Props, State> {
- private dashboard: DashboardModel;
- constructor(props: Props) {
- super(props);
- this.dashboard = props.dashboard;
- this.state = {
- isLoading: false,
- step: 1,
- selectedExpireOption: expireOptions[0],
- snapshotExpires: 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(),
- };
- if (!external) {
- this.dashboard.snapshot.originalUrl = window.location.href;
- }
- 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 {
- reportInteraction('grafana_dashboards_snapshot_created', {
- location: external ? 'raintank' : 'local',
- });
- 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: any) => {
- variable.query = '';
- variable.options = variable.current ? [variable.current] : [];
- 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<HTMLInputElement>) => {
- this.setState({ snapshotName: event.target.value });
- };
- onTimeoutChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({ timeoutSeconds: Number(event.target.value) });
- };
- onExpireChange = (option: SelectableValue<number>) => {
- this.setState({
- selectedExpireOption: option,
- snapshotExpires: option.value,
- });
- };
- onSnapshotUrlCopy = () => {
- appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
- };
- renderStep1() {
- const { onDismiss } = this.props;
- const { snapshotName, selectedExpireOption, timeoutSeconds, isLoading, sharingButtonText, externalEnabled } =
- this.state;
- return (
- <>
- <div>
- <p className="share-modal-info-text">
- 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.
- </p>
- <p className="share-modal-info-text">
- Keep in mind, your snapshot <em>can be viewed by anyone</em> that has the link and can access the URL. Share
- wisely.
- </p>
- </div>
- <Field label="Snapshot name">
- <Input id="snapshot-name-input" width={30} value={snapshotName} onChange={this.onSnapshotNameChange} />
- </Field>
- <Field label="Expire">
- <Select
- inputId="expire-select-input"
- width={30}
- options={expireOptions}
- value={selectedExpireOption}
- onChange={this.onExpireChange}
- />
- </Field>
- <Field
- label="Timeout (seconds)"
- description="You might need to configure the timeout value if it takes a long time to collect your dashboard
- metrics."
- >
- <Input id="timeout-input" type="number" width={21} value={timeoutSeconds} onChange={this.onTimeoutChange} />
- </Field>
- <Modal.ButtonRow>
- <Button variant="secondary" onClick={onDismiss} fill="outline">
- Cancel
- </Button>
- {externalEnabled && (
- <Button variant="secondary" disabled={isLoading} onClick={this.createSnapshot(true)}>
- {sharingButtonText}
- </Button>
- )}
- <Button variant="primary" disabled={isLoading} onClick={this.createSnapshot()}>
- Local Snapshot
- </Button>
- </Modal.ButtonRow>
- </>
- );
- }
- renderStep2() {
- const { snapshotUrl } = this.state;
- return (
- <>
- <div className="gf-form" style={{ marginTop: '40px' }}>
- <div className="gf-form-row">
- <a href={snapshotUrl} className="large share-modal-link" target="_blank" rel="noreferrer">
- <Icon name="external-link-alt" /> {snapshotUrl}
- </a>
- <br />
- <ClipboardButton variant="secondary" getText={this.getSnapshotUrl} onClipboardCopy={this.onSnapshotUrlCopy}>
- Copy Link
- </ClipboardButton>
- </div>
- </div>
- <div className="pull-right" style={{ padding: '5px' }}>
- Did you make a mistake?{' '}
- <LinkButton fill="text" target="_blank" onClick={this.deleteSnapshot}>
- Delete snapshot.
- </LinkButton>
- </div>
- </>
- );
- }
- renderStep3() {
- return (
- <div className="share-modal-header">
- <p className="share-modal-info-text">
- 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.
- </p>
- </div>
- );
- }
- render() {
- const { isLoading, step } = this.state;
- return (
- <>
- {step === 1 && this.renderStep1()}
- {step === 2 && this.renderStep2()}
- {step === 3 && this.renderStep3()}
- {isLoading && <Spinner inline={true} />}
- </>
- );
- }
- }
|