details.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  5. import {openModal} from 'sentry/actionCreators/modal';
  6. import Button from 'sentry/components/actions/button';
  7. import {Alert} from 'sentry/components/alert';
  8. import Confirm from 'sentry/components/confirm';
  9. import Form from 'sentry/components/forms/form';
  10. import FormField from 'sentry/components/forms/formField';
  11. import JsonForm from 'sentry/components/forms/jsonForm';
  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 TextCopyInput from 'sentry/components/textCopyInput';
  16. import apiApplication from 'sentry/data/forms/apiApplication';
  17. import {t} from 'sentry/locale';
  18. import ConfigStore from 'sentry/stores/configStore';
  19. import type {ApiApplication} from 'sentry/types';
  20. import getDynamicText from 'sentry/utils/getDynamicText';
  21. import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
  22. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  23. type Props = RouteComponentProps<{appId: string}, {}>;
  24. type State = {
  25. app: ApiApplication;
  26. } & DeprecatedAsyncView['state'];
  27. class ApiApplicationsDetails extends DeprecatedAsyncView<Props, State> {
  28. rotateClientSecret = async () => {
  29. try {
  30. const rotateResponse = await this.api.requestPromise(
  31. `/api-applications/${this.props.params.appId}/rotate-secret/`,
  32. {
  33. method: 'POST',
  34. }
  35. );
  36. openModal(({Body, Header}) => (
  37. <Fragment>
  38. <Header>{t('Your new Client Secret')}</Header>
  39. <Body>
  40. <Alert type="info" showIcon>
  41. {t('This will be the only time your client secret is visible!')}
  42. </Alert>
  43. <TextCopyInput aria-label={t('new-client-secret')}>
  44. {rotateResponse.clientSecret}
  45. </TextCopyInput>
  46. </Body>
  47. </Fragment>
  48. ));
  49. } catch {
  50. addErrorMessage(t('Error rotating secret'));
  51. }
  52. };
  53. getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
  54. return [['app', `/api-applications/${this.props.params.appId}/`]];
  55. }
  56. getTitle() {
  57. return t('Application Details');
  58. }
  59. renderBody() {
  60. const urlPrefix = ConfigStore.get('urlPrefix');
  61. return (
  62. <div>
  63. <SettingsPageHeader title={this.getTitle()} />
  64. <Form
  65. apiMethod="PUT"
  66. apiEndpoint={`/api-applications/${this.props.params.appId}/`}
  67. saveOnBlur
  68. allowUndo
  69. initialData={this.state.app}
  70. onSubmitError={() => addErrorMessage('Unable to save change')}
  71. >
  72. <JsonForm forms={apiApplication} />
  73. <Panel>
  74. <PanelHeader>{t('Credentials')}</PanelHeader>
  75. <PanelBody>
  76. <FormField name="clientID" label="Client ID">
  77. {({value}) => (
  78. <div>
  79. <TextCopyInput>
  80. {getDynamicText({value, fixed: 'CI_CLIENT_ID'})}
  81. </TextCopyInput>
  82. </div>
  83. )}
  84. </FormField>
  85. <FormField
  86. name="clientSecret"
  87. label="Client Secret"
  88. help={t(`Your secret is only available briefly after application creation. Make
  89. sure to save this value!`)}
  90. >
  91. {({value}) =>
  92. value ? (
  93. <TextCopyInput>
  94. {getDynamicText({value, fixed: 'CI_CLIENT_SECRET'})}
  95. </TextCopyInput>
  96. ) : (
  97. <ClientSecret>
  98. <HiddenSecret>{t('hidden')}</HiddenSecret>
  99. <Confirm
  100. onConfirm={this.rotateClientSecret}
  101. message={t(
  102. 'Are you sure you want to rotate the client secret? The current one will not be usable anymore, and this cannot be undone.'
  103. )}
  104. >
  105. <Button priority="danger">Rotate client secret</Button>
  106. </Confirm>
  107. </ClientSecret>
  108. )
  109. }
  110. </FormField>
  111. <FormField name="" label="Authorization URL">
  112. {() => <TextCopyInput>{`${urlPrefix}/oauth/authorize/`}</TextCopyInput>}
  113. </FormField>
  114. <FormField name="" label="Token URL">
  115. {() => <TextCopyInput>{`${urlPrefix}/oauth/token/`}</TextCopyInput>}
  116. </FormField>
  117. </PanelBody>
  118. </Panel>
  119. </Form>
  120. </div>
  121. );
  122. }
  123. }
  124. const HiddenSecret = styled('span')`
  125. width: 100px;
  126. font-style: italic;
  127. `;
  128. const ClientSecret = styled('div')`
  129. display: flex;
  130. justify-content: right;
  131. align-items: center;
  132. margin-right: 0;
  133. `;
  134. export default ApiApplicationsDetails;