authTokenRow.tsx 4.3 KB

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