auditLogList.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import UserAvatar from 'sentry/components/avatar/userAvatar';
  4. import DateTime from 'sentry/components/dateTime';
  5. import SelectControl from 'sentry/components/forms/selectControl';
  6. import Pagination, {CursorHandler} from 'sentry/components/pagination';
  7. import {PanelTable} from 'sentry/components/panels';
  8. import Tooltip from 'sentry/components/tooltip';
  9. import {t} from 'sentry/locale';
  10. import space from 'sentry/styles/space';
  11. import {AuditLog} from 'sentry/types';
  12. import {shouldUse24Hours} from 'sentry/utils/dates';
  13. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  14. const avatarStyle = {
  15. width: 36,
  16. height: 36,
  17. marginRight: 8,
  18. };
  19. type Props = {
  20. entries: AuditLog[] | null;
  21. eventType: string | undefined;
  22. eventTypes: string[];
  23. isLoading: boolean;
  24. onCursor: CursorHandler | undefined;
  25. onEventSelect: (value: string) => void;
  26. pageLinks: string | null;
  27. };
  28. const AuditLogList = ({
  29. isLoading,
  30. pageLinks,
  31. entries,
  32. eventTypes,
  33. onCursor,
  34. onEventSelect,
  35. }: Props) => {
  36. const is24Hours = shouldUse24Hours();
  37. const hasEntries = entries && entries.length > 0;
  38. const ipv4Length = 15;
  39. const eventOptions = eventTypes.map(type => ({
  40. label: type,
  41. value: type,
  42. }));
  43. const action = (
  44. <EventSelector
  45. clearable
  46. isDisabled={isLoading}
  47. name="eventFilter"
  48. placeholder={t('Select Action: ')}
  49. options={eventOptions}
  50. onChange={options => {
  51. onEventSelect(options?.value);
  52. }}
  53. />
  54. );
  55. return (
  56. <div>
  57. <SettingsPageHeader title={t('Audit Log')} action={action} />
  58. <PanelTable
  59. headers={[t('Member'), t('Action'), t('IP'), t('Time')]}
  60. isEmpty={!hasEntries && entries?.length === 0}
  61. emptyMessage={t('No audit entries available')}
  62. isLoading={isLoading}
  63. >
  64. {entries?.map(entry => (
  65. <Fragment key={entry.id}>
  66. <UserInfo>
  67. <div>
  68. {entry.actor.email && (
  69. <UserAvatar style={avatarStyle} user={entry.actor} />
  70. )}
  71. </div>
  72. <NameContainer>
  73. <Name data-test-id="actor-name">
  74. {entry.actor.isSuperuser
  75. ? t('%s (Sentry Staff)', entry.actor.name)
  76. : entry.actor.name}
  77. </Name>
  78. <Note>{entry.note}</Note>
  79. </NameContainer>
  80. </UserInfo>
  81. <FlexCenter>
  82. <MonoDetail>{entry.event}</MonoDetail>
  83. </FlexCenter>
  84. <FlexCenter>
  85. {entry.ipAddress && (
  86. <IpAddressOverflow>
  87. <Tooltip
  88. title={entry.ipAddress}
  89. disabled={entry.ipAddress.length <= ipv4Length}
  90. >
  91. <MonoDetail>{entry.ipAddress}</MonoDetail>
  92. </Tooltip>
  93. </IpAddressOverflow>
  94. )}
  95. </FlexCenter>
  96. <TimestampInfo>
  97. <DateTime dateOnly date={entry.dateCreated} />
  98. <DateTime
  99. timeOnly
  100. format={is24Hours ? 'HH:mm zz' : 'LT zz'}
  101. date={entry.dateCreated}
  102. />
  103. </TimestampInfo>
  104. </Fragment>
  105. ))}
  106. </PanelTable>
  107. {pageLinks && <Pagination pageLinks={pageLinks} onCursor={onCursor} />}
  108. </div>
  109. );
  110. };
  111. const EventSelector = styled(SelectControl)`
  112. width: 250px;
  113. `;
  114. const UserInfo = styled('div')`
  115. display: flex;
  116. align-items: center;
  117. line-height: 1.2;
  118. font-size: 13px;
  119. min-width: 250px;
  120. `;
  121. const NameContainer = styled('div')`
  122. display: flex;
  123. flex-direction: column;
  124. justify-content: center;
  125. `;
  126. const Name = styled('div')`
  127. font-weight: 600;
  128. font-size: 15px;
  129. `;
  130. const Note = styled('div')`
  131. font-size: 13px;
  132. word-break: break-word;
  133. `;
  134. const FlexCenter = styled('div')`
  135. display: flex;
  136. align-items: center;
  137. `;
  138. const IpAddressOverflow = styled('div')`
  139. ${p => p.theme.overflowEllipsis};
  140. min-width: 90px;
  141. `;
  142. const MonoDetail = styled('code')`
  143. font-size: ${p => p.theme.fontSizeMedium};
  144. white-space: no-wrap;
  145. `;
  146. const TimestampInfo = styled('div')`
  147. display: grid;
  148. grid-template-rows: auto auto;
  149. gap: ${space(1)};
  150. font-size: ${p => p.theme.fontSizeMedium};
  151. `;
  152. export default AuditLogList;