authTokenRow.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import Confirm from 'sentry/components/confirm';
  5. import Link from 'sentry/components/links/link';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import Placeholder from 'sentry/components/placeholder';
  8. import TimeSince from 'sentry/components/timeSince';
  9. import {Tooltip} from 'sentry/components/tooltip';
  10. import {IconSubtract} from 'sentry/icons';
  11. import {t, tct} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {Organization} from 'sentry/types/organization';
  14. import type {Project} from 'sentry/types/project';
  15. import type {OrgAuthToken} from 'sentry/types/user';
  16. import getDynamicText from 'sentry/utils/getDynamicText';
  17. import {tokenPreview} from 'sentry/views/settings/organizationAuthTokens';
  18. function LastUsed({
  19. organization,
  20. dateLastUsed,
  21. projectLastUsed,
  22. }: {
  23. organization: Organization;
  24. dateLastUsed?: Date;
  25. projectLastUsed?: Project;
  26. }) {
  27. if (dateLastUsed && projectLastUsed) {
  28. return (
  29. <Fragment>
  30. {tct('[date] in project [project]', {
  31. date: (
  32. <TimeSince
  33. date={getDynamicText({
  34. value: dateLastUsed,
  35. fixed: new Date(1508208080000), // National Pasta Day
  36. })}
  37. />
  38. ),
  39. project: (
  40. <Link to={`/settings/${organization.slug}/${projectLastUsed.slug}/`}>
  41. {projectLastUsed.name}
  42. </Link>
  43. ),
  44. })}
  45. </Fragment>
  46. );
  47. }
  48. if (dateLastUsed) {
  49. return (
  50. <Fragment>
  51. <TimeSince
  52. date={getDynamicText({
  53. value: dateLastUsed,
  54. fixed: new Date(1508208080000), // National Pasta Day
  55. })}
  56. />
  57. </Fragment>
  58. );
  59. }
  60. if (projectLastUsed) {
  61. return (
  62. <Fragment>
  63. {tct('in project [project]', {
  64. project: (
  65. <Link to={`/settings/${organization.slug}/${projectLastUsed.slug}/`}>
  66. {projectLastUsed.name}
  67. </Link>
  68. ),
  69. })}
  70. </Fragment>
  71. );
  72. }
  73. return <NeverUsed>{t('never used')}</NeverUsed>;
  74. }
  75. export function OrganizationAuthTokensAuthTokenRow({
  76. organization,
  77. isRevoking,
  78. token,
  79. revokeToken,
  80. projectLastUsed,
  81. isProjectLoading,
  82. }: {
  83. isRevoking: boolean;
  84. organization: Organization;
  85. token: OrgAuthToken;
  86. isProjectLoading?: boolean;
  87. projectLastUsed?: Project;
  88. revokeToken?: (token: OrgAuthToken) => void;
  89. }) {
  90. return (
  91. <Fragment>
  92. <div>
  93. <Label>
  94. <Link to={`/settings/${organization.slug}/auth-tokens/${token.id}/`}>
  95. {token.name}
  96. </Link>
  97. </Label>
  98. {token.tokenLastCharacters && (
  99. <TokenPreview aria-label={t('Token preview')}>
  100. {tokenPreview(
  101. getDynamicText({
  102. value: token.tokenLastCharacters,
  103. fixed: 'ABCD',
  104. }),
  105. 'sntrys_'
  106. )}
  107. </TokenPreview>
  108. )}
  109. </div>
  110. <DateTime>
  111. {isProjectLoading ? (
  112. <Placeholder height="1.25em" />
  113. ) : (
  114. <Fragment>
  115. <TimeSince
  116. date={getDynamicText({
  117. value: token.dateCreated,
  118. fixed: new Date(1508208080000), // National Pasta Day
  119. })}
  120. />
  121. </Fragment>
  122. )}
  123. </DateTime>
  124. <DateTime>
  125. {isProjectLoading ? (
  126. <Placeholder height="1.25em" />
  127. ) : (
  128. <LastUsed
  129. dateLastUsed={token.dateLastUsed}
  130. projectLastUsed={projectLastUsed}
  131. organization={organization}
  132. />
  133. )}
  134. </DateTime>
  135. <Actions>
  136. <Tooltip
  137. title={t(
  138. 'You must be an organization owner, manager or admin to revoke a token.'
  139. )}
  140. disabled={!!revokeToken}
  141. >
  142. <Confirm
  143. disabled={!revokeToken || isRevoking}
  144. onConfirm={revokeToken ? () => revokeToken(token) : undefined}
  145. message={t(
  146. 'Are you sure you want to revoke %s token? It will not be usable anymore, and this cannot be undone.',
  147. tokenPreview(token.tokenLastCharacters || '', 'sntrys_')
  148. )}
  149. >
  150. <Button
  151. size="sm"
  152. disabled={isRevoking || !revokeToken}
  153. aria-label={t('Revoke %s', token.name)}
  154. icon={
  155. isRevoking ? (
  156. <LoadingIndicator mini />
  157. ) : (
  158. <IconSubtract isCircled size="xs" />
  159. )
  160. }
  161. >
  162. {t('Revoke')}
  163. </Button>
  164. </Confirm>
  165. </Tooltip>
  166. </Actions>
  167. </Fragment>
  168. );
  169. }
  170. const Label = styled('div')``;
  171. const Actions = styled('div')`
  172. display: flex;
  173. justify-content: flex-end;
  174. `;
  175. const DateTime = styled('div')`
  176. display: flex;
  177. align-items: center;
  178. gap: ${space(0.5)};
  179. `;
  180. const NeverUsed = styled('div')`
  181. color: ${p => p.theme.gray300};
  182. `;
  183. const TokenPreview = styled('div')`
  184. color: ${p => p.theme.gray300};
  185. `;