apiTokenDetails.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import {
  2. addErrorMessage,
  3. addLoadingMessage,
  4. addSuccessMessage,
  5. } from 'sentry/actionCreators/indicator';
  6. import FieldGroup from 'sentry/components/forms/fieldGroup';
  7. import TextField from 'sentry/components/forms/fields/textField';
  8. import Form from 'sentry/components/forms/form';
  9. import ExternalLink from 'sentry/components/links/externalLink';
  10. import LoadingError from 'sentry/components/loadingError';
  11. import LoadingIndicator from 'sentry/components/loadingIndicator';
  12. import Panel from 'sentry/components/panels/panel';
  13. import PanelBody from 'sentry/components/panels/panelBody';
  14. import PanelHeader from 'sentry/components/panels/panelHeader';
  15. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  16. import {t, tct} from 'sentry/locale';
  17. import type {InternalAppApiToken} from 'sentry/types/user';
  18. import {browserHistory} from 'sentry/utils/browserHistory';
  19. import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
  20. import {useApiQuery} from 'sentry/utils/queryClient';
  21. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  22. import useMutateApiToken from 'sentry/utils/useMutateApiToken';
  23. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  24. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  25. import {tokenPreview} from 'sentry/views/settings/organizationAuthTokens';
  26. const API_INDEX_ROUTE = '/settings/account/api/auth-tokens/';
  27. type Props = {
  28. params: {tokenId: string};
  29. };
  30. type FetchApiTokenParameters = {
  31. tokenId: string;
  32. };
  33. type FetchApiTokenResponse = InternalAppApiToken;
  34. export const makeFetchApiTokenKey = ({tokenId}: FetchApiTokenParameters) =>
  35. [`/api-tokens/${tokenId}/`] as const;
  36. function ApiTokenDetailsForm({token}: {token: InternalAppApiToken}) {
  37. const initialData = {
  38. name: token.name,
  39. tokenPreview: tokenPreview(token.tokenLastCharacters || '****'),
  40. };
  41. const handleGoBack = () => {
  42. browserHistory.push(normalizeUrl(API_INDEX_ROUTE));
  43. };
  44. const onSuccess = () => {
  45. addSuccessMessage(t('Updated user auth token.'));
  46. handleGoBack();
  47. };
  48. const onError = error => {
  49. const message = t('Failed to update the user auth token.');
  50. handleXhrErrorResponse(message, error);
  51. addErrorMessage(message);
  52. };
  53. const {mutate: submitToken} = useMutateApiToken({
  54. token: token,
  55. onSuccess: onSuccess,
  56. onError: onError,
  57. });
  58. return (
  59. <Form
  60. apiMethod="PUT"
  61. initialData={initialData}
  62. apiEndpoint={`/api-tokens/${token.id}/`}
  63. onSubmit={({name}) => {
  64. addLoadingMessage();
  65. return submitToken({
  66. name,
  67. });
  68. }}
  69. onCancel={handleGoBack}
  70. >
  71. <TextField
  72. name="name"
  73. label={t('Name')}
  74. help={t('A name to help you identify this token.')}
  75. />
  76. <TextField
  77. name="tokenPreview"
  78. label={t('Token')}
  79. disabled
  80. help={t('You can only view the token once after creation.')}
  81. />
  82. <FieldGroup
  83. label={t('Scopes')}
  84. help={t('You cannot change the scopes of an existing token.')}
  85. >
  86. <div>{token.scopes.slice().sort().join(', ')}</div>
  87. </FieldGroup>
  88. </Form>
  89. );
  90. }
  91. export function ApiTokenDetails({params}: Props) {
  92. const {tokenId} = params;
  93. const {
  94. isPending,
  95. isError,
  96. data: token,
  97. refetch: refetchToken,
  98. } = useApiQuery<FetchApiTokenResponse>(makeFetchApiTokenKey({tokenId}), {
  99. staleTime: Infinity,
  100. });
  101. return (
  102. <div>
  103. <SentryDocumentTitle title={t('Edit User Auth Token')} />
  104. <SettingsPageHeader title={t('Edit User Auth Token')} />
  105. <TextBlock>
  106. {t(
  107. "Authentication tokens allow you to perform actions against the Sentry API on behalf of your account. They're the easiest way to get started using the API."
  108. )}
  109. </TextBlock>
  110. <TextBlock>
  111. {tct(
  112. 'For more information on how to use the web API, see our [link:documentation].',
  113. {
  114. link: <ExternalLink href="https://docs.sentry.io/api/" />,
  115. }
  116. )}
  117. </TextBlock>
  118. <Panel>
  119. <PanelHeader>{t('User Auth Token Details')}</PanelHeader>
  120. <PanelBody>
  121. {isError && (
  122. <LoadingError
  123. message={t('Failed to load user auth token.')}
  124. onRetry={refetchToken}
  125. />
  126. )}
  127. {isPending && <LoadingIndicator />}
  128. {!isPending && !isError && token && <ApiTokenDetailsForm token={token} />}
  129. </PanelBody>
  130. </Panel>
  131. </div>
  132. );
  133. }
  134. export default ApiTokenDetails;