details.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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 Form from 'sentry/components/forms/form';
  9. import FormField from 'sentry/components/forms/formField';
  10. import JsonForm from 'sentry/components/forms/jsonForm';
  11. import Panel from 'sentry/components/panels/panel';
  12. import PanelBody from 'sentry/components/panels/panelBody';
  13. import PanelHeader from 'sentry/components/panels/panelHeader';
  14. import TextCopyInput from 'sentry/components/textCopyInput';
  15. import apiApplication from 'sentry/data/forms/apiApplication';
  16. import {t} from 'sentry/locale';
  17. import ConfigStore from 'sentry/stores/configStore';
  18. import type {ApiApplication} from 'sentry/types';
  19. import getDynamicText from 'sentry/utils/getDynamicText';
  20. import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
  21. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  22. type Props = RouteComponentProps<{appId: string}, {}>;
  23. type State = {
  24. app: ApiApplication;
  25. } & DeprecatedAsyncView['state'];
  26. class ApiApplicationsDetails extends DeprecatedAsyncView<Props, State> {
  27. rotateClientSecret = async () => {
  28. try {
  29. const rotateResponse = await this.api.requestPromise(
  30. `/api-applications/${this.props.params.appId}/rotate-secret/`,
  31. {
  32. method: 'POST',
  33. }
  34. );
  35. openModal(({Body, Header}) => (
  36. <Fragment>
  37. <Header>{t('Rotated Client Secret')}</Header>
  38. <Body>
  39. <Alert type="info" showIcon>
  40. {t('This will be the only time your client secret is visible!')}
  41. </Alert>
  42. <p>
  43. {t('Your client secret is:')}
  44. <code>{rotateResponse.clientSecret}</code>
  45. </p>
  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. <Button onClick={this.rotateClientSecret} priority="danger">
  100. Rotate client secret
  101. </Button>
  102. </ClientSecret>
  103. )
  104. }
  105. </FormField>
  106. <FormField name="" label="Authorization URL">
  107. {() => <TextCopyInput>{`${urlPrefix}/oauth/authorize/`}</TextCopyInput>}
  108. </FormField>
  109. <FormField name="" label="Token URL">
  110. {() => <TextCopyInput>{`${urlPrefix}/oauth/token/`}</TextCopyInput>}
  111. </FormField>
  112. </PanelBody>
  113. </Panel>
  114. </Form>
  115. </div>
  116. );
  117. }
  118. }
  119. const HiddenSecret = styled('span')`
  120. width: 100px;
  121. font-style: italic;
  122. `;
  123. const ClientSecret = styled('div')`
  124. display: flex;
  125. justify-content: right;
  126. align-items: center;
  127. margin-right: 0;
  128. `;
  129. export default ApiApplicationsDetails;