apiTokenRow.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {Link} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import Confirm from 'sentry/components/confirm';
  5. import {DateTime} from 'sentry/components/dateTime';
  6. import PanelItem from 'sentry/components/panels/panelItem';
  7. import {IconSubtract} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {InternalAppApiToken} from 'sentry/types';
  11. import getDynamicText from 'sentry/utils/getDynamicText';
  12. import {tokenPreview} from 'sentry/views/settings/organizationAuthTokens';
  13. type Props = {
  14. onRemove: (token: InternalAppApiToken) => void;
  15. token: InternalAppApiToken;
  16. tokenPrefix?: string;
  17. };
  18. function ApiTokenRow({token, onRemove, tokenPrefix = ''}: Props) {
  19. return (
  20. <StyledPanelItem>
  21. <Controls>
  22. <LinkWrapper name={token.name}>
  23. <Link to={`/settings/account/api/auth-tokens/${token.id}/`}>
  24. {token.name ? token.name : 'Token created on '}
  25. <DateTime
  26. date={getDynamicText({
  27. value: token.dateCreated,
  28. fixed: new Date(1508208080000), // National Pasta Day
  29. })}
  30. hidden={!!token.name}
  31. />
  32. </Link>
  33. </LinkWrapper>
  34. <ButtonWrapper>
  35. <Confirm
  36. onConfirm={() => onRemove(token)}
  37. message={t(
  38. 'Are you sure you want to revoke %s token? It will not be usable anymore, and this cannot be undone.',
  39. tokenPreview(token.tokenLastCharacters, tokenPrefix)
  40. )}
  41. >
  42. <Button
  43. data-test-id="token-delete"
  44. icon={<IconSubtract isCircled size="xs" />}
  45. >
  46. {t('Remove')}
  47. </Button>
  48. </Confirm>
  49. </ButtonWrapper>
  50. </Controls>
  51. <Details>
  52. <TokenWrapper>
  53. <Heading>{t('Token')}</Heading>
  54. <TokenPreview aria-label={t('Token preview')}>
  55. {tokenPreview(
  56. getDynamicText({
  57. value: token.tokenLastCharacters,
  58. fixed: 'ABCD',
  59. }),
  60. tokenPrefix
  61. )}
  62. </TokenPreview>
  63. </TokenWrapper>
  64. <ScopesWrapper>
  65. <Heading>{t('Scopes')}</Heading>
  66. <ScopeList>{token.scopes.join(', ')}</ScopeList>
  67. </ScopesWrapper>
  68. <div>
  69. <Heading>{t('Created')}</Heading>
  70. <Time>
  71. <DateTime
  72. date={getDynamicText({
  73. value: token.dateCreated,
  74. fixed: new Date(1508208080000), // National Pasta Day
  75. })}
  76. />
  77. </Time>
  78. </div>
  79. </Details>
  80. </StyledPanelItem>
  81. );
  82. }
  83. const StyledPanelItem = styled(PanelItem)`
  84. flex-direction: column;
  85. padding: ${space(2)};
  86. `;
  87. const Controls = styled('div')`
  88. display: flex;
  89. align-items: center;
  90. margin-bottom: ${space(1)};
  91. `;
  92. const Details = styled('div')`
  93. display: flex;
  94. margin-top: ${space(1)};
  95. `;
  96. const TokenWrapper = styled('div')`
  97. flex: 1;
  98. margin-right: ${space(1)};
  99. `;
  100. const ScopesWrapper = styled('div')`
  101. flex: 2;
  102. margin-right: ${space(4)};
  103. `;
  104. const ScopeList = styled('div')`
  105. font-size: ${p => p.theme.fontSizeRelativeSmall};
  106. line-height: 1.4;
  107. `;
  108. const Time = styled('time')`
  109. font-size: ${p => p.theme.fontSizeRelativeSmall};
  110. line-height: 1.4;
  111. `;
  112. const Heading = styled('div')`
  113. font-size: ${p => p.theme.fontSizeMedium};
  114. text-transform: uppercase;
  115. color: ${p => p.theme.subText};
  116. margin-bottom: ${space(1)};
  117. `;
  118. const TokenPreview = styled('div')`
  119. color: ${p => p.theme.gray300};
  120. `;
  121. const LinkWrapper = styled('div')<{name: string}>`
  122. font-style: ${p => (p.name ? 'normal' : 'italic')};
  123. `;
  124. const ButtonWrapper = styled('div')`
  125. margin-left: auto;
  126. display: flex;
  127. flex-direction: column;
  128. align-items: flex-end;
  129. justify-content: flex-end;
  130. font-size: ${p => p.theme.fontSizeSmall};
  131. gap: ${space(1)};
  132. `;
  133. export default ApiTokenRow;