u2fEnrolledDetails.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {Fragment, useState} from 'react';
  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 EmptyMessage from 'sentry/components/emptyMessage';
  7. import Input from 'sentry/components/input';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelBody from 'sentry/components/panels/panelBody';
  10. import PanelHeader from 'sentry/components/panels/panelHeader';
  11. import PanelItem from 'sentry/components/panels/panelItem';
  12. import {Tooltip} from 'sentry/components/tooltip';
  13. import {IconClose, IconDelete} from 'sentry/icons';
  14. import {t} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import ConfirmHeader from 'sentry/views/settings/account/accountSecurity/components/confirmHeader';
  17. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  18. function U2fEnrolledDetails(props) {
  19. const {className, isEnrolled, devices, id, onRemoveU2fDevice, onRenameU2fDevice} =
  20. props;
  21. if (id !== 'u2f' || !isEnrolled) {
  22. return null;
  23. }
  24. const hasDevices = devices?.length;
  25. // Note Tooltip doesn't work because of bootstrap(?) pointer events for disabled buttons
  26. const isLastDevice = hasDevices === 1;
  27. return (
  28. <Panel className={className}>
  29. <PanelHeader>{t('Device name')}</PanelHeader>
  30. <PanelBody>
  31. {!hasDevices && (
  32. <EmptyMessage>{t('You have not added any U2F devices')}</EmptyMessage>
  33. )}
  34. {hasDevices &&
  35. devices?.map((device, i) => (
  36. <Device
  37. key={i}
  38. device={device}
  39. isLastDevice={isLastDevice}
  40. onRenameU2fDevice={onRenameU2fDevice}
  41. onRemoveU2fDevice={onRemoveU2fDevice}
  42. />
  43. ))}
  44. <AddAnotherPanelItem>
  45. <Button to="/settings/account/security/mfa/u2f/enroll/" size="sm">
  46. {t('Add Another Device')}
  47. </Button>
  48. </AddAnotherPanelItem>
  49. </PanelBody>
  50. </Panel>
  51. );
  52. }
  53. function Device(props) {
  54. const {device, isLastDevice, onRenameU2fDevice, onRemoveU2fDevice} = props;
  55. const [deviceName, setDeviceName] = useState(device.name);
  56. const [isEditing, setEditting] = useState(false);
  57. if (!isEditing) {
  58. return (
  59. <DevicePanelItem key={device.name}>
  60. <DeviceInformation>
  61. <Name>{device.name}</Name>
  62. <FadedDateTime date={device.timestamp} />
  63. </DeviceInformation>
  64. <Actions>
  65. <Button size="sm" onClick={() => setEditting(true)}>
  66. {t('Rename Device')}
  67. </Button>
  68. </Actions>
  69. <Actions>
  70. <Confirm
  71. onConfirm={() => onRemoveU2fDevice(device)}
  72. disabled={isLastDevice}
  73. message={
  74. <Fragment>
  75. <ConfirmHeader>{t('Do you want to remove U2F device?')}</ConfirmHeader>
  76. <TextBlock>
  77. {t('Are you sure you want to remove the U2F device "%s"?', device.name)}
  78. </TextBlock>
  79. </Fragment>
  80. }
  81. >
  82. <Button size="sm" priority="danger">
  83. <Tooltip
  84. disabled={!isLastDevice}
  85. title={t('Can not remove last U2F device')}
  86. >
  87. <IconDelete size="xs" />
  88. </Tooltip>
  89. </Button>
  90. </Confirm>
  91. </Actions>
  92. </DevicePanelItem>
  93. );
  94. }
  95. return (
  96. <DevicePanelItem key={device.name}>
  97. <DeviceInformation>
  98. <DeviceNameInput
  99. type="text"
  100. value={deviceName}
  101. onChange={e => {
  102. setDeviceName(e.target.value);
  103. }}
  104. />
  105. <FadedDateTime date={device.timestamp} />
  106. </DeviceInformation>
  107. <Actions>
  108. <Button
  109. priority="primary"
  110. size="sm"
  111. onClick={() => {
  112. onRenameU2fDevice(device, deviceName);
  113. setEditting(false);
  114. }}
  115. >
  116. Rename Device
  117. </Button>
  118. </Actions>
  119. <Actions>
  120. <Button
  121. size="sm"
  122. title="Cancel rename"
  123. onClick={() => {
  124. setDeviceName(device.name);
  125. setEditting(false);
  126. }}
  127. >
  128. <IconClose size="xs" />
  129. </Button>
  130. </Actions>
  131. </DevicePanelItem>
  132. );
  133. }
  134. const DeviceNameInput = styled(Input)`
  135. width: 50%;
  136. margin-right: ${space(2)};
  137. `;
  138. const DevicePanelItem = styled(PanelItem)`
  139. padding: 0;
  140. `;
  141. const DeviceInformation = styled('div')`
  142. display: flex;
  143. align-items: center;
  144. justify-content: space-between;
  145. flex: 1 1;
  146. height: 72px;
  147. padding: ${space(2)};
  148. padding-right: 0;
  149. `;
  150. const FadedDateTime = styled(DateTime)`
  151. font-size: ${p => p.theme.fontSizeRelativeSmall};
  152. opacity: 0.6;
  153. `;
  154. const Name = styled('div')`
  155. flex: 1;
  156. `;
  157. const Actions = styled('div')`
  158. margin: ${space(2)};
  159. `;
  160. const AddAnotherPanelItem = styled(PanelItem)`
  161. justify-content: flex-end;
  162. padding: ${space(2)};
  163. `;
  164. export default styled(U2fEnrolledDetails)`
  165. margin-top: ${space(4)};
  166. `;