The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx

288 lines
9.5 KiB

import useAsyncFn from 'react-use/lib/useAsyncFn';
import { SelectableValue } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { getBackendSrv } from '@grafana/runtime';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { Button, ClipboardButton, Field, Input, Modal, RadioButtonGroup, Stack } from '@grafana/ui';
import { notifyApp } from 'app/core/actions';
import { createSuccessNotification } from 'app/core/copy/appNotification';
import { t, Trans } from 'app/core/internationalization';
import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv';
import { dispatch } from 'app/store/store';
import { DashboardScene } from '../scene/DashboardScene';
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
import { DashboardInteractions } from '../utils/interactions';
import { SceneShareTabState, ShareView } from './types';
const selectors = e2eSelectors.pages.ShareDashboardModal.SnapshotScene;
export const getExpireOptions = () => {
const DEFAULT_EXPIRE_OPTION: SelectableValue<number> = {
label: t('share-modal.snapshot.expire-week', '1 Week'),
value: 60 * 60 * 24 * 7,
};
return [
{
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,
},
DEFAULT_EXPIRE_OPTION,
{
label: t('share-modal.snapshot.expire-never', `Never`),
value: 0,
},
];
};
const getDefaultExpireOption = () => {
return getExpireOptions()[2];
};
export interface ShareSnapshotTabState extends SceneShareTabState {
dashboardRef: SceneObjectRef<DashboardScene>;
panelRef?: SceneObjectRef<VizPanel>;
snapshotName: string;
selectedExpireOption: SelectableValue<number>;
snapshotSharingOptions?: SnapshotSharingOptions;
}
export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> implements ShareView {
public tabId = shareDashboardType.snapshot;
static Component = ShareSnapshotTabRenderer;
public constructor(
state: Omit<ShareSnapshotTabState, 'snapshotName' | 'selectedExpireOption' | 'snapshotSharingOptions'>
) {
super({
...state,
snapshotName: state.dashboardRef.resolve().state.title,
selectedExpireOption: getDefaultExpireOption(),
});
this.addActivationHandler(() => {
this._onActivate();
});
}
private _onActivate() {
getDashboardSnapshotSrv()
.getSharingOptions()
.then((shareOptions) => {
if (this.isActive) {
this.setState({
snapshotSharingOptions: shareOptions,
});
}
});
}
public getTabLabel() {
return t('share-modal.tab-title.snapshot', 'Snapshot');
}
public onSnasphotNameChange = (snapshotName: string) => {
this.setState({ snapshotName });
};
public onExpireChange = (option: number) => {
this.setState({
selectedExpireOption: getExpireOptions().find((o) => o.value === option),
});
};
private prepareSnapshot() {
const timeRange = sceneGraph.getTimeRange(this);
const { dashboardRef, panelRef } = this.state;
const saveModel = transformSceneToSaveModel(dashboardRef.resolve(), true);
return trimDashboardForSnapshot(
this.state.snapshotName.trim() || '',
timeRange.state.value,
saveModel,
panelRef?.resolve()
);
}
public onSnapshotCreate = async (external = false) => {
const { selectedExpireOption } = this.state;
const snapshot = this.prepareSnapshot();
// TODO
// snapshot.snapshot = {
// originalUrl: window.location.href,
// };
const cmdData = {
dashboard: snapshot,
name: snapshot.title,
expires: selectedExpireOption?.value,
external,
};
try {
const response = await getDashboardSnapshotSrv().create(cmdData);
dispatch(
notifyApp(createSuccessNotification(t('snapshot.share.success-creation', 'Your snapshot has been created')))
);
return response;
} finally {
if (external) {
DashboardInteractions.publishSnapshotClicked({
expires: cmdData.expires,
shareResource: getTrackingSource(this.state.panelRef),
});
} else {
DashboardInteractions.publishSnapshotLocalClicked({
expires: cmdData.expires,
shareResource: getTrackingSource(this.state.panelRef),
});
}
}
};
public onSnapshotDelete = async (url: string) => {
const response = await getBackendSrv().get(url);
dispatch(
notifyApp(createSuccessNotification(t('snapshot.share.success-delete', 'Your snapshot has been deleted')))
);
return response;
};
}
function ShareSnapshotTabRenderer({ model }: SceneComponentProps<ShareSnapshotTab>) {
const { snapshotName, selectedExpireOption, modalRef, snapshotSharingOptions } = model.useState();
const [snapshotResult, createSnapshot] = useAsyncFn(async (external = false) => {
return model.onSnapshotCreate(external);
});
const [deleteSnapshotResult, deleteSnapshot] = useAsyncFn(async (url: string) => {
return await getBackendSrv().get(url);
});
// If snapshot has been deleted - show message and allow to close modal
if (deleteSnapshotResult.value) {
return (
<Trans i18nKey="share-modal.snapshot.deleted-message">
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.
</Trans>
);
}
return (
<>
{/* Before snapshot has been created show configuration */}
{!Boolean(snapshotResult.value) && (
<>
<div>
<p>
<Trans i18nKey="share-modal.snapshot.info-text-1">
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.
</Trans>
</p>
<p>
<Trans i18nKey="share-modal.snapshot.info-text-2">
Keep in mind, your snapshot <em>can be viewed by anyone</em> that has the link and can access the URL.
Share wisely.
</Trans>
</p>
</div>
<Field label={t('share-modal.snapshot.name', `Snapshot name`)}>
<Input
id="snapshot-name-input"
width={30}
defaultValue={snapshotName}
onBlur={(e) => model.onSnasphotNameChange(e.target.value)}
/>
</Field>
<Field label={t('share-modal.snapshot.expire', `Expire`)}>
<RadioButtonGroup<number>
id="expire-select-input"
options={getExpireOptions()}
value={selectedExpireOption?.value}
onChange={model.onExpireChange}
/>
</Field>
<Modal.ButtonRow>
<Button
variant="secondary"
onClick={() => {
modalRef?.resolve().onDismiss();
}}
fill="outline"
>
<Trans i18nKey="share-modal.snapshot.cancel-button">Cancel</Trans>
</Button>
{snapshotSharingOptions?.externalEnabled && (
<Button variant="secondary" disabled={snapshotResult.loading} onClick={() => createSnapshot(true)}>
{snapshotSharingOptions?.externalSnapshotName}
</Button>
)}
<Button
variant="primary"
disabled={snapshotResult.loading}
onClick={() => createSnapshot()}
data-testid={selectors.PublishSnapshot}
>
<Trans i18nKey="share-modal.snapshot.local-button">Publish Snapshot</Trans>
</Button>
</Modal.ButtonRow>
</>
)}
{/* When snapshot has been created - show link and allow copy/deletion */}
{snapshotResult.value && (
<Stack direction="column" gap={0}>
<Field label={t('share-modal.snapshot.url-label', 'Snapshot URL')}>
<Input
data-testid={selectors.CopyUrlInput}
id="snapshot-url-input"
value={snapshotResult.value.url}
readOnly
addonAfter={
<ClipboardButton
data-testid={selectors.CopyUrlButton}
icon="copy"
variant="primary"
getText={() => snapshotResult.value!.url}
>
<Trans i18nKey="share-modal.snapshot.copy-link-button">Copy</Trans>
</ClipboardButton>
}
/>
</Field>
<div style={{ alignSelf: 'flex-end', padding: '5px' }}>
<Trans i18nKey="share-modal.snapshot.mistake-message">Did you make a mistake? </Trans>&nbsp;
<Button
fill="outline"
size="md"
variant="destructive"
onClick={() => {
deleteSnapshot(snapshotResult.value!.deleteUrl);
}}
>
<Trans i18nKey="share-modal.snapshot.delete-button">Delete snapshot.</Trans>
</Button>
</div>
</Stack>
)}
</>
);
}