relayWrapper.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {useCallback, useState} from 'react';
  2. import omit from 'lodash/omit';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {openModal} from 'sentry/actionCreators/modal';
  5. import {Button} from 'sentry/components/button';
  6. import ExternalLink from 'sentry/components/links/externalLink';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  10. import {IconAdd} from 'sentry/icons';
  11. import {t, tct} from 'sentry/locale';
  12. import type {Organization} from 'sentry/types/organization';
  13. import type {Relay, RelayActivity} from 'sentry/types/relay';
  14. import {useApiQuery} from 'sentry/utils/queryClient';
  15. import useApi from 'sentry/utils/useApi';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  18. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  19. import PermissionAlert from 'sentry/views/settings/organization/permissionAlert';
  20. import Add from './modals/add';
  21. import Edit from './modals/edit';
  22. import EmptyState from './emptyState';
  23. import List from './list';
  24. const RELAY_DOCS_LINK = 'https://getsentry.github.io/relay/';
  25. export function RelayWrapper() {
  26. const organization = useOrganization();
  27. const api = useApi();
  28. const [relays, setRelays] = useState<Relay[]>(organization.trustedRelays);
  29. const disabled = !organization.access.includes('org:write');
  30. const handleOpenAddDialog = useCallback(() => {
  31. openModal(modalProps => (
  32. <Add
  33. {...modalProps}
  34. savedRelays={relays}
  35. api={api}
  36. orgSlug={organization.slug}
  37. onSubmitSuccess={response => {
  38. addSuccessMessage(t('Successfully added Relay public key'));
  39. setRelays(response.trustedRelays);
  40. }}
  41. />
  42. ));
  43. }, [relays, api, organization.slug]);
  44. return (
  45. <SentryDocumentTitle title={t('Relay')}>
  46. <SettingsPageHeader
  47. title={t('Relay')}
  48. action={
  49. <Button
  50. title={
  51. disabled ? t('You do not have permission to register keys') : undefined
  52. }
  53. priority="primary"
  54. size="sm"
  55. icon={<IconAdd isCircled />}
  56. onClick={handleOpenAddDialog}
  57. disabled={disabled}
  58. >
  59. {t('Register Key')}
  60. </Button>
  61. }
  62. />
  63. <PermissionAlert />
  64. <TextBlock>
  65. {tct(
  66. 'Sentry Relay offers enterprise-grade data security by providing a standalone service that acts as a middle layer between your application and sentry.io. Go to [link:Relay Documentation] for setup and details.',
  67. {link: <ExternalLink href={RELAY_DOCS_LINK} />}
  68. )}
  69. </TextBlock>
  70. {relays.length === 0 ? (
  71. <EmptyState />
  72. ) : (
  73. <RelayUsageList
  74. orgSlug={organization.slug}
  75. disabled={disabled}
  76. relays={relays}
  77. api={api}
  78. onRelaysChange={setRelays}
  79. />
  80. )}
  81. </SentryDocumentTitle>
  82. );
  83. }
  84. function RelayUsageList({
  85. relays,
  86. orgSlug,
  87. disabled,
  88. api,
  89. onRelaysChange,
  90. }: {
  91. api: ReturnType<typeof useApi>;
  92. disabled: boolean;
  93. onRelaysChange: (relays: Relay[]) => void;
  94. orgSlug: Organization['slug'];
  95. relays: Relay[];
  96. }) {
  97. const {isPending, isError, refetch, data} = useApiQuery<RelayActivity[]>(
  98. [`/organizations/${orgSlug}/relay_usage/`],
  99. {
  100. staleTime: 0,
  101. retry: false,
  102. enabled: relays.length > 0,
  103. }
  104. );
  105. const handleOpenEditDialog = useCallback(
  106. (publicKey: string) => {
  107. const editRelay = relays.find(relay => relay.publicKey === publicKey);
  108. if (!editRelay) {
  109. return;
  110. }
  111. openModal(modalProps => (
  112. <Edit
  113. {...modalProps}
  114. savedRelays={relays}
  115. api={api}
  116. orgSlug={orgSlug}
  117. relay={editRelay}
  118. onSubmitSuccess={response => {
  119. addSuccessMessage(t('Successfully updated Relay public key'));
  120. onRelaysChange(response.trustedRelays);
  121. }}
  122. />
  123. ));
  124. },
  125. [orgSlug, relays, api, onRelaysChange]
  126. );
  127. const handleDeleteRelay = useCallback(
  128. async (publicKey: string) => {
  129. const trustedRelays = relays
  130. .filter(relay => relay.publicKey !== publicKey)
  131. .map(relay => omit(relay, ['created', 'lastModified']));
  132. try {
  133. const response = await api.requestPromise(`/organizations/${orgSlug}/`, {
  134. method: 'PUT',
  135. data: {trustedRelays},
  136. });
  137. addSuccessMessage(t('Successfully deleted Relay public key'));
  138. onRelaysChange(response.trustedRelays);
  139. } catch {
  140. addErrorMessage(t('An unknown error occurred while deleting Relay public key'));
  141. }
  142. },
  143. [relays, api, orgSlug, onRelaysChange]
  144. );
  145. if (isPending) {
  146. return <LoadingIndicator />;
  147. }
  148. if (isError) {
  149. return <LoadingError onRetry={refetch} />;
  150. }
  151. return (
  152. <List
  153. relays={relays}
  154. relayActivities={data}
  155. disabled={disabled}
  156. onEdit={publicKey => () => handleOpenEditDialog(publicKey)}
  157. onRefresh={() => refetch()}
  158. onDelete={publicKey => () => handleDeleteRelay(publicKey)}
  159. />
  160. );
  161. }