organizationApiKeysList.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import {Fragment} from 'react';
  2. import {PlainRoute, RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import AlertLink from 'sentry/components/alertLink';
  5. import AutoSelectText from 'sentry/components/autoSelectText';
  6. import Button from 'sentry/components/button';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import Link from 'sentry/components/links/link';
  9. import LinkWithConfirmation from 'sentry/components/links/linkWithConfirmation';
  10. import {PanelTable} from 'sentry/components/panels';
  11. import {IconAdd, IconDelete} from 'sentry/icons';
  12. import {t, tct} from 'sentry/locale';
  13. import {inputStyles} from 'sentry/styles/input';
  14. import recreateRoute from 'sentry/utils/recreateRoute';
  15. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  16. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  17. import {DeprecatedApiKey} from './types';
  18. type RouteParams = {
  19. orgId: string;
  20. };
  21. type Props = RouteComponentProps<RouteParams, {}> & {
  22. /**
  23. * Busy differs from loading in that busy is a result of an action like removing
  24. */
  25. busy: boolean;
  26. keys: DeprecatedApiKey[];
  27. /**
  28. * Loading refers to fetching the API Keys
  29. */
  30. loading: boolean;
  31. onAddApiKey: () => {};
  32. onRemove: (id: DeprecatedApiKey['id']) => {};
  33. routes: PlainRoute[];
  34. };
  35. function OrganizationApiKeysList({
  36. params,
  37. routes,
  38. keys,
  39. busy,
  40. loading,
  41. onAddApiKey,
  42. onRemove,
  43. }: Props) {
  44. const hasKeys = keys && keys.length;
  45. const action = (
  46. <Button
  47. priority="primary"
  48. size="sm"
  49. icon={<IconAdd size="xs" isCircled />}
  50. busy={busy}
  51. disabled={busy}
  52. onClick={onAddApiKey}
  53. >
  54. {t('New API Key')}
  55. </Button>
  56. );
  57. return (
  58. <div>
  59. <SettingsPageHeader title={t('API Keys')} action={action} />
  60. <TextBlock>
  61. {tct(
  62. `API keys grant access to the [api:developer web API].
  63. If you're looking to configure a Sentry client, you'll need a
  64. client key which is available in your project settings.`,
  65. {
  66. api: <ExternalLink href="https://docs.sentry.io/api/" />,
  67. }
  68. )}
  69. </TextBlock>
  70. <AlertLink to="/settings/account/api/auth-tokens/" priority="info">
  71. {tct(
  72. 'Until Sentry supports OAuth, you might want to switch to using [tokens:Auth Tokens] instead.',
  73. {
  74. tokens: <u />,
  75. }
  76. )}
  77. </AlertLink>
  78. <PanelTable
  79. isLoading={loading}
  80. isEmpty={!hasKeys}
  81. emptyMessage={t('No API keys for this organization')}
  82. headers={[t('Name'), t('Key'), t('Actions')]}
  83. >
  84. {keys &&
  85. keys.map(({id, key, label}) => {
  86. const apiDetailsUrl = recreateRoute(`${id}/`, {
  87. params,
  88. routes,
  89. });
  90. return (
  91. <Fragment key={key}>
  92. <Cell>
  93. <Link to={apiDetailsUrl}>{label}</Link>
  94. </Cell>
  95. <div>
  96. <AutoSelectTextInput readOnly>{key}</AutoSelectTextInput>
  97. </div>
  98. <Cell>
  99. <LinkWithConfirmation
  100. aria-label={t('Remove API Key')}
  101. className="btn btn-default btn-sm"
  102. onConfirm={() => onRemove(id)}
  103. message={t('Are you sure you want to remove this API key?')}
  104. title={t('Remove API Key?')}
  105. >
  106. <IconDelete size="xs" css={{position: 'relative', top: '2px'}} />
  107. </LinkWithConfirmation>
  108. </Cell>
  109. </Fragment>
  110. );
  111. })}
  112. </PanelTable>
  113. </div>
  114. );
  115. }
  116. const Cell = styled('div')`
  117. display: flex;
  118. align-items: center;
  119. `;
  120. const AutoSelectTextInput = styled(AutoSelectText)<{readOnly: boolean}>`
  121. ${p => inputStyles(p)}
  122. `;
  123. export default OrganizationApiKeysList;