|
@@ -5,14 +5,17 @@ import ActivityAvatar from 'sentry/components/activity/item/avatar';
|
|
|
import UserAvatar from 'sentry/components/avatar/userAvatar';
|
|
|
import DateTime from 'sentry/components/dateTime';
|
|
|
import SelectControl from 'sentry/components/forms/selectControl';
|
|
|
+import Link from 'sentry/components/links/link';
|
|
|
import Pagination, {CursorHandler} from 'sentry/components/pagination';
|
|
|
import {PanelTable} from 'sentry/components/panels';
|
|
|
import Tag from 'sentry/components/tag';
|
|
|
import Tooltip from 'sentry/components/tooltip';
|
|
|
-import {t} from 'sentry/locale';
|
|
|
+import {t, tct} from 'sentry/locale';
|
|
|
import space from 'sentry/styles/space';
|
|
|
-import {AuditLog, User} from 'sentry/types';
|
|
|
+import {AuditLog, Organization, User} from 'sentry/types';
|
|
|
import {shouldUse24Hours} from 'sentry/utils/dates';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+import useProjects from 'sentry/utils/useProjects';
|
|
|
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
|
|
|
|
|
|
const avatarStyle = {
|
|
@@ -51,6 +54,105 @@ const addUsernameDisplay = (logEntryUser: User | undefined) => {
|
|
|
return null;
|
|
|
};
|
|
|
|
|
|
+function AuditNote({entry, orgSlug}: {entry: AuditLog; orgSlug: Organization['slug']}) {
|
|
|
+ const {projects} = useProjects();
|
|
|
+ const project = projects.find(p => p.id === String(entry.data.id));
|
|
|
+
|
|
|
+ if (!project) {
|
|
|
+ return <Note>{entry.note}</Note>;
|
|
|
+ }
|
|
|
+
|
|
|
+ const projectSlug = (
|
|
|
+ <Link to={`/settings/${orgSlug}/projects/${project.slug}/`}>{entry.data.slug}</Link>
|
|
|
+ );
|
|
|
+
|
|
|
+ if (entry.event === 'project.create') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Created project [project-slug]', {['project-slug']: projectSlug})}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'project.edit') {
|
|
|
+ if (entry.data.old_slug && entry.data.new_slug) {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Renamed project slug from [old-slug] to [new-slug]', {
|
|
|
+ 'old-slug': entry.data.old_slug,
|
|
|
+ 'new-slug': (
|
|
|
+ <Link to={`/settings/${orgSlug}/projects/${entry.data.new_slug}/`}>
|
|
|
+ {entry.data.new_slug}
|
|
|
+ </Link>
|
|
|
+ ),
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Edited project [project-slug] [note]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ note: entry.note.replace('edited project settings ', ''),
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'sampling.add') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Added server-side sampling rule in the project [project-slug]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'sampling.remove') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Deleted server-side sampling rule in the project [project-slug]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'sampling.edit') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Edited server-side sampling rule in the project [project-slug]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'sampling.activate') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Enabled server-side sampling rule in the project [project-slug]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entry.event === 'sampling.deactivate') {
|
|
|
+ return (
|
|
|
+ <Note>
|
|
|
+ {tct('Disabled server-side sampling rule in the project [project-slug]', {
|
|
|
+ ['project-slug']: projectSlug,
|
|
|
+ })}
|
|
|
+ </Note>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return <Note>{entry.note}</Note>;
|
|
|
+}
|
|
|
+
|
|
|
type Props = {
|
|
|
entries: AuditLog[] | null;
|
|
|
eventType: string | undefined;
|
|
@@ -71,6 +173,7 @@ const AuditLogList = ({
|
|
|
onEventSelect,
|
|
|
}: Props) => {
|
|
|
const is24Hours = shouldUse24Hours();
|
|
|
+ const organization = useOrganization();
|
|
|
const hasEntries = entries && entries.length > 0;
|
|
|
const ipv4Length = 15;
|
|
|
|
|
@@ -102,40 +205,42 @@ const AuditLogList = ({
|
|
|
emptyMessage={t('No audit entries available')}
|
|
|
isLoading={isLoading}
|
|
|
>
|
|
|
- {entries?.map(entry => (
|
|
|
- <Fragment key={entry.id}>
|
|
|
- <UserInfo>
|
|
|
- <div>{getAvatarDisplay(entry.actor)}</div>
|
|
|
- <NameContainer>
|
|
|
- {addUsernameDisplay(entry.actor)}
|
|
|
- <Note>{entry.note}</Note>
|
|
|
- </NameContainer>
|
|
|
- </UserInfo>
|
|
|
- <FlexCenter>
|
|
|
- <MonoDetail>{entry.event}</MonoDetail>
|
|
|
- </FlexCenter>
|
|
|
- <FlexCenter>
|
|
|
- {entry.ipAddress && (
|
|
|
- <IpAddressOverflow>
|
|
|
- <Tooltip
|
|
|
- title={entry.ipAddress}
|
|
|
- disabled={entry.ipAddress.length <= ipv4Length}
|
|
|
- >
|
|
|
- <MonoDetail>{entry.ipAddress}</MonoDetail>
|
|
|
- </Tooltip>
|
|
|
- </IpAddressOverflow>
|
|
|
- )}
|
|
|
- </FlexCenter>
|
|
|
- <TimestampInfo>
|
|
|
- <DateTime dateOnly date={entry.dateCreated} />
|
|
|
- <DateTime
|
|
|
- timeOnly
|
|
|
- format={is24Hours ? 'HH:mm zz' : 'LT zz'}
|
|
|
- date={entry.dateCreated}
|
|
|
- />
|
|
|
- </TimestampInfo>
|
|
|
- </Fragment>
|
|
|
- ))}
|
|
|
+ {entries?.map(entry => {
|
|
|
+ return (
|
|
|
+ <Fragment key={entry.id}>
|
|
|
+ <UserInfo>
|
|
|
+ <div>{getAvatarDisplay(entry.actor)}</div>
|
|
|
+ <NameContainer>
|
|
|
+ {addUsernameDisplay(entry.actor)}
|
|
|
+ <AuditNote entry={entry} orgSlug={organization.slug} />
|
|
|
+ </NameContainer>
|
|
|
+ </UserInfo>
|
|
|
+ <FlexCenter>
|
|
|
+ <MonoDetail>{entry.event}</MonoDetail>
|
|
|
+ </FlexCenter>
|
|
|
+ <FlexCenter>
|
|
|
+ {entry.ipAddress && (
|
|
|
+ <IpAddressOverflow>
|
|
|
+ <Tooltip
|
|
|
+ title={entry.ipAddress}
|
|
|
+ disabled={entry.ipAddress.length <= ipv4Length}
|
|
|
+ >
|
|
|
+ <MonoDetail>{entry.ipAddress}</MonoDetail>
|
|
|
+ </Tooltip>
|
|
|
+ </IpAddressOverflow>
|
|
|
+ )}
|
|
|
+ </FlexCenter>
|
|
|
+ <TimestampInfo>
|
|
|
+ <DateTime dateOnly date={entry.dateCreated} />
|
|
|
+ <DateTime
|
|
|
+ timeOnly
|
|
|
+ format={is24Hours ? 'HH:mm zz' : 'LT zz'}
|
|
|
+ date={entry.dateCreated}
|
|
|
+ />
|
|
|
+ </TimestampInfo>
|
|
|
+ </Fragment>
|
|
|
+ );
|
|
|
+ })}
|
|
|
</PanelTable>
|
|
|
{pageLinks && <Pagination pageLinks={pageLinks} onCursor={onCursor} />}
|
|
|
</div>
|