ShareLink.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import React, { PureComponent } from 'react';
  2. import { AppEvents, SelectableValue } from '@grafana/data';
  3. import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
  4. import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, RadioButtonGroup, Switch } from '@grafana/ui';
  5. import config from 'app/core/config';
  6. import { appEvents } from 'app/core/core';
  7. import { ShareModalTabProps } from './types';
  8. import { buildImageUrl, buildShareUrl } from './utils';
  9. const themeOptions: Array<SelectableValue<string>> = [
  10. { label: 'Current', value: 'current' },
  11. { label: 'Dark', value: 'dark' },
  12. { label: 'Light', value: 'light' },
  13. ];
  14. export interface Props extends ShareModalTabProps {}
  15. export interface State {
  16. useCurrentTimeRange: boolean;
  17. useShortUrl: boolean;
  18. selectedTheme: string;
  19. shareUrl: string;
  20. imageUrl: string;
  21. }
  22. export class ShareLink extends PureComponent<Props, State> {
  23. constructor(props: Props) {
  24. super(props);
  25. this.state = {
  26. useCurrentTimeRange: true,
  27. useShortUrl: false,
  28. selectedTheme: 'current',
  29. shareUrl: '',
  30. imageUrl: '',
  31. };
  32. }
  33. componentDidMount() {
  34. this.buildUrl();
  35. }
  36. componentDidUpdate(prevProps: Props, prevState: State) {
  37. const { useCurrentTimeRange, useShortUrl, selectedTheme } = this.state;
  38. if (
  39. prevState.useCurrentTimeRange !== useCurrentTimeRange ||
  40. prevState.selectedTheme !== selectedTheme ||
  41. prevState.useShortUrl !== useShortUrl
  42. ) {
  43. this.buildUrl();
  44. }
  45. }
  46. buildUrl = async () => {
  47. const { panel, dashboard } = this.props;
  48. const { useCurrentTimeRange, useShortUrl, selectedTheme } = this.state;
  49. const shareUrl = await buildShareUrl(useCurrentTimeRange, selectedTheme, panel, useShortUrl);
  50. const imageUrl = buildImageUrl(useCurrentTimeRange, dashboard.uid, selectedTheme, panel);
  51. this.setState({ shareUrl, imageUrl });
  52. };
  53. onUseCurrentTimeRangeChange = () => {
  54. this.setState({ useCurrentTimeRange: !this.state.useCurrentTimeRange });
  55. };
  56. onUrlShorten = () => {
  57. this.setState({ useShortUrl: !this.state.useShortUrl });
  58. };
  59. onThemeChange = (value: string) => {
  60. this.setState({ selectedTheme: value });
  61. };
  62. onShareUrlCopy = () => {
  63. appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
  64. };
  65. getShareUrl = () => {
  66. return this.state.shareUrl;
  67. };
  68. render() {
  69. const { panel, dashboard } = this.props;
  70. const isRelativeTime = dashboard ? dashboard.time.to === 'now' : false;
  71. const { useCurrentTimeRange, useShortUrl, selectedTheme, shareUrl, imageUrl } = this.state;
  72. const selectors = e2eSelectors.pages.SharePanelModal;
  73. const isDashboardSaved = Boolean(dashboard.id);
  74. return (
  75. <>
  76. <p className="share-modal-info-text">
  77. Create a direct link to this dashboard or panel, customized with the options below.
  78. </p>
  79. <FieldSet>
  80. <Field
  81. label="Lock time range"
  82. description={isRelativeTime ? 'Transforms the current relative time range to an absolute time range' : ''}
  83. >
  84. <Switch
  85. id="share-current-time-range"
  86. value={useCurrentTimeRange}
  87. onChange={this.onUseCurrentTimeRangeChange}
  88. />
  89. </Field>
  90. <Field label="Theme">
  91. <RadioButtonGroup options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
  92. </Field>
  93. <Field label="Shorten URL">
  94. <Switch id="share-shorten-url" value={useShortUrl} onChange={this.onUrlShorten} />
  95. </Field>
  96. <Field label="Link URL">
  97. <Input
  98. id="link-url-input"
  99. value={shareUrl}
  100. readOnly
  101. addonAfter={
  102. <ClipboardButton variant="primary" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}>
  103. <Icon name="copy" /> Copy
  104. </ClipboardButton>
  105. }
  106. />
  107. </Field>
  108. </FieldSet>
  109. {panel && config.rendererAvailable && (
  110. <>
  111. {isDashboardSaved && (
  112. <div className="gf-form">
  113. <a href={imageUrl} target="_blank" rel="noreferrer" aria-label={selectors.linkToRenderedImage}>
  114. <Icon name="camera" /> Direct link rendered image
  115. </a>
  116. </div>
  117. )}
  118. {!isDashboardSaved && (
  119. <Alert severity="info" title="Dashboard is not saved" bottomSpacing={0}>
  120. To render a panel image, you must save the dashboard first.
  121. </Alert>
  122. )}
  123. </>
  124. )}
  125. {panel && !config.rendererAvailable && (
  126. <Alert severity="info" title="Image renderer plugin not installed" bottomSpacing={0}>
  127. <>To render a panel image, you must install the </>
  128. <a
  129. href="https://grafana.com/grafana/plugins/grafana-image-renderer"
  130. target="_blank"
  131. rel="noopener noreferrer"
  132. className="external-link"
  133. >
  134. Grafana image renderer plugin
  135. </a>
  136. . Please contact your Grafana administrator to install the plugin.
  137. </Alert>
  138. )}
  139. </>
  140. );
  141. }
  142. }