Skip to content

Commit

Permalink
Merge pull request strapi#15536 from strapi/feature/audit-logs
Browse files Browse the repository at this point in the history
  • Loading branch information
Convly authored Jan 25, 2023
2 parents 85a9359 + 10274e4 commit de2e6f2
Show file tree
Hide file tree
Showing 66 changed files with 2,839 additions and 555 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module.exports = {
beforeUpdate() {
const ctx = strapi.requestContext.get();

console.log('User info in service: ', ctx.state.user);
},
};
10 changes: 7 additions & 3 deletions packages/core/admin/admin/src/components/LeftMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { NavLink as RouterNavLink, useLocation } from 'react-router-dom';
import { NavLink as RouterNavLink, useLocation, useHistory } from 'react-router-dom';
import { Divider } from '@strapi/design-system/Divider';
import {
MainNav,
Expand All @@ -21,7 +21,8 @@ import { Stack } from '@strapi/design-system/Stack';
import Write from '@strapi/icons/Write';
import Exit from '@strapi/icons/Exit';
import { auth, usePersistentState, useAppInfos, useTracking } from '@strapi/helper-plugin';
import useConfigurations from '../../hooks/useConfigurations';
import { useConfigurations } from '../../hooks';
import { axiosInstance } from '../../core/utils';

const LinkUserWrapper = styled(Box)`
width: ${150 / 16}rem;
Expand Down Expand Up @@ -62,6 +63,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const { pathname } = useLocation();
const history = useHistory();

const initials = userDisplayName
.split(' ')
Expand All @@ -71,9 +73,11 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {

const handleToggleUserLinks = () => setUserLinksVisible((prev) => !prev);

const handleLogout = () => {
const handleLogout = async () => {
await axiosInstance.post('/admin/logout');
auth.clearAppStorage();
handleToggleUserLinks();
history.push('/auth/login');
};

const handleBlur = (e) => {
Expand Down
20 changes: 2 additions & 18 deletions packages/core/admin/admin/src/hooks/useSettingsMenu/init.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import omit from 'lodash/omit';
import sortLinks from './utils/sortLinks';
import adminPermissions from '../../permissions';
import formatLinks from './utils/formatLinks';
import adminLinks from './utils/adminLinks';
import globalLinks from './utils/globalLinks';

const init = (initialState, { settings, shouldUpdateStrapi }) => {
Expand All @@ -23,23 +23,7 @@ const init = (initialState, { settings, shouldUpdateStrapi }) => {
{
id: 'permissions',
intlLabel: { id: 'Settings.permissions', defaultMessage: 'Administration Panel' },
links: [
{
intlLabel: { id: 'global.roles', defaultMessage: 'Roles' },
to: '/settings/roles',
id: 'roles',
isDisplayed: false,
permissions: adminPermissions.settings.roles.main,
},
{
intlLabel: { id: 'global.users' },
// Init the search params directly
to: '/settings/users?pageSize=10&page=1&sort=firstname',
id: 'users',
isDisplayed: false,
permissions: adminPermissions.settings.users.main,
},
],
links: adminLinks,
},
...otherSections,
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import customAdminLinks from 'ee_else_ce/hooks/useSettingsMenu/utils/customAdminLinks';
import defaultAdminLinks from './defaultAdminLinks';

export default [...customAdminLinks, ...defaultAdminLinks];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default [];
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import adminPermissions from '../../../permissions';

const defaultAdminLinks = [
{
intlLabel: { id: 'global.roles', defaultMessage: 'Roles' },
to: '/settings/roles',
id: 'roles',
isDisplayed: false,
permissions: adminPermissions.settings.roles.main,
},
{
intlLabel: { id: 'global.users' },
// Init the search params directly
to: '/settings/users?pageSize=10&page=1&sort=firstname',
id: 'users',
isDisplayed: false,
permissions: adminPermissions.settings.users.main,
},
];

export default defaultAdminLinks;
1 change: 1 addition & 0 deletions packages/core/admin/admin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ window.strapi = {
telemetryDisabled: process.env.STRAPI_TELEMETRY_DISABLED ?? false,
features: {
SSO: 'sso',
auditLogs: 'audit-logs',
},
projectType: 'Community',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const permissions = {
uninstall: [{ action: 'admin::marketplace.plugins.uninstall', subject: null }],
},
settings: {
auditLogs: {
main: [{ action: 'admin::audit-logs.read', subject: null }],
read: [{ action: 'admin::audit-logs.read', subject: null }],
},
roles: {
main: [
{ action: 'admin::roles.create', subject: null },
Expand Down
34 changes: 34 additions & 0 deletions packages/core/admin/admin/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,38 @@
"Settings.permissions.users.strapi-super-admin": "Super Admin",
"Settings.permissions.users.strapi-editor": "Editor",
"Settings.permissions.users.strapi-author": "Author",
"Settings.permissions.auditLogs.action": "Action",
"Settings.permissions.auditLogs.date": "Date",
"Settings.permissions.auditLogs.user": "User",
"Settings.permissions.auditLogs.userId": "User ID",
"Settings.permissions.auditLogs.details": "Log Details",
"Settings.permissions.auditLogs.payload": "Payload",
"Settings.permissions.auditLogs.listview.header.subtitle": "Logs of all the activities that happened in your environment",
"Settings.permissions.auditLogs.entry.create": "Create entry ({model})",
"Settings.permissions.auditLogs.entry.update": "Update entry ({model})",
"Settings.permissions.auditLogs.entry.delete": "Delete entry ({model})",
"Settings.permissions.auditLogs.entry.publish": "Publish entry ({model})",
"Settings.permissions.auditLogs.entry.unpublish": "Unpublish entry ({model})",
"Settings.permissions.auditLogs.media.create": "Create media",
"Settings.permissions.auditLogs.media.update": "Update media",
"Settings.permissions.auditLogs.media.delete": "Delete media",
"Settings.permissions.auditLogs.user.create": "Create user",
"Settings.permissions.auditLogs.user.update": "Update user",
"Settings.permissions.auditLogs.user.delete": "Delete user",
"Settings.permissions.auditLogs.admin.auth.success": "Admin login",
"Settings.permissions.auditLogs.admin.logout": "Admin logout",
"Settings.permissions.auditLogs.content-type.create": "Create content type",
"Settings.permissions.auditLogs.content-type.update": "Update content type",
"Settings.permissions.auditLogs.content-type.delete": "Delete content type",
"Settings.permissions.auditLogs.component.create": "Create component",
"Settings.permissions.auditLogs.component.update": "Update component",
"Settings.permissions.auditLogs.component.delete": "Delete component",
"Settings.permissions.auditLogs.role.create": "Create role",
"Settings.permissions.auditLogs.role.update": "Update role",
"Settings.permissions.auditLogs.role.delete": "Delete role",
"Settings.permissions.auditLogs.permission.create": "Create permission",
"Settings.permissions.auditLogs.permission.update": "Update permission",
"Settings.permissions.auditLogs.permission.delete": "Delete permission",
"Settings.profile.form.notify.data.loaded": "Your profile data has been loaded",
"Settings.profile.form.section.experience.clear.select": "Clear the interface language selected",
"Settings.profile.form.section.experience.here": "here",
Expand Down Expand Up @@ -306,6 +338,7 @@
"app.component.search.label": "Search for {target}",
"app.component.table.duplicate": "Duplicate {target}",
"app.component.table.edit": "Edit {target}",
"app.component.table.view": "{target} details",
"app.component.table.select.one-entry": "Select {target}",
"app.components.BlockLink.blog": "Blog",
"app.components.BlockLink.blog.content": "Read the latest news about Strapi and the ecosystem.",
Expand Down Expand Up @@ -792,6 +825,7 @@
"global.settings": "Settings",
"global.type": "Type",
"global.users": "Users",
"global.auditLogs": "Audit Logs",
"notification.contentType.relations.conflict": "Content type has conflicting relations",
"notification.default.title": "Information:",
"notification.error": "An error occurred",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import adminPermissions from '../../../../../admin/src/permissions';

const auditLogsRoutes = strapi.features.isEnabled(strapi.features.auditLogs)
? [
{
intlLabel: { id: 'global.auditLogs', defaultMessage: 'Audit Logs' },
to: '/settings/audit-logs?pageSize=50&page=1&sort=date:DESC',
id: 'auditLogs',
isDisplayed: false,
permissions: adminPermissions.settings.auditLogs.main,
},
]
: [];

const customAdminLinks = [...auditLogsRoutes];

export default customAdminLinks;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { Loader } from '@strapi/design-system/Loader';
import { Grid } from '@strapi/design-system/Grid';
import { Box } from '@strapi/design-system/Box';
import { Flex } from '@strapi/design-system/Flex';
import { Typography } from '@strapi/design-system/Typography';
import { JSONInput } from '@strapi/design-system/JSONInput';
import { pxToRem } from '@strapi/helper-plugin';
import getDefaultMessage from '../utils/getActionTypesDefaultMessages';
import ActionItem from './ActionItem';

const ActionBody = ({ status, data, formattedDate }) => {
const { formatMessage } = useIntl();

if (status === 'loading') {
return (
<Flex padding={7} justifyContent="center" alignItems="center">
<Loader>Loading content...</Loader>
</Flex>
);
}

const { action, user, payload } = data;

return (
<>
<Box marginBottom={3}>
<Typography variant="delta" id="title">
{formatMessage({
id: 'Settings.permissions.auditLogs.details',
defaultMessage: 'Log Details',
})}
</Typography>
</Box>
<Grid
gap={4}
gridCols={2}
paddingTop={4}
paddingBottom={4}
paddingLeft={6}
paddingRight={6}
marginBottom={4}
background="neutral100"
hasRadius
>
<ActionItem
actionLabel={formatMessage({
id: 'Settings.permissions.auditLogs.action',
defaultMessage: 'Action',
})}
actionName={formatMessage(
{
id: `Settings.permissions.auditLogs.${action}`,
defaultMessage: getDefaultMessage(action),
},
{ model: payload?.model }
)}
/>
<ActionItem
actionLabel={formatMessage({
id: 'Settings.permissions.auditLogs.date',
defaultMessage: 'Date',
})}
actionName={formattedDate}
/>
<ActionItem
actionLabel={formatMessage({
id: 'Settings.permissions.auditLogs.user',
defaultMessage: 'User',
})}
actionName={user?.fullname || '-'}
/>
<ActionItem
actionLabel={formatMessage({
id: 'Settings.permissions.auditLogs.userId',
defaultMessage: 'User ID',
})}
actionName={user?.id.toString() || '-'}
/>
</Grid>
<JSONInput
value={JSON.stringify(payload, null, 2)}
disabled
height={pxToRem(150)}
label={formatMessage({
id: 'Settings.permissions.auditLogs.payload',
defaultMessage: 'Payload',
})}
/>
</>
);
};

ActionBody.defaultProps = {
data: {},
};

ActionBody.propTypes = {
status: PropTypes.oneOf(['idle', 'loading', 'error', 'success']).isRequired,
data: PropTypes.shape({
action: PropTypes.string,
date: PropTypes.string,
payload: PropTypes.object,
user: PropTypes.object,
}),
formattedDate: PropTypes.string.isRequired,
};

export default ActionBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Flex } from '@strapi/design-system/Flex';
import { Typography } from '@strapi/design-system/Typography';

const ActionItem = ({ actionLabel, actionName }) => {
return (
<Flex direction="column" alignItems="baseline" gap={1}>
<Typography textColor="neutral600" variant="sigma">
{actionLabel}
</Typography>
<Typography textColor="neutral600">{actionName}</Typography>
</Flex>
);
};

ActionItem.propTypes = {
actionLabel: PropTypes.string.isRequired,
actionName: PropTypes.string.isRequired,
};

export default ActionItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useQuery } from 'react-query';
import { ModalLayout, ModalHeader, ModalBody } from '@strapi/design-system/ModalLayout';
import { Breadcrumbs, Crumb } from '@strapi/design-system/Breadcrumbs';
import { useNotification, useFetchClient } from '@strapi/helper-plugin';
import useFormatTimeStamp from '../hooks/useFormatTimeStamp';
import ActionBody from './ActionBody';

const Modal = ({ handleClose, logId }) => {
const { get } = useFetchClient();
const toggleNotification = useNotification();

const fetchAuditLog = async (id) => {
const { data } = await get(`/admin/audit-logs/${id}`);

if (!data) {
throw new Error('Audit log not found');
}

return data;
};

const { data, status } = useQuery(['audit-log', logId], () => fetchAuditLog(logId), {
onError() {
toggleNotification({
type: 'warning',
message: { id: 'notification.error', defaultMessage: 'An error occured' },
});
handleClose();
},
});

const formatTimeStamp = useFormatTimeStamp();
const formattedDate = data ? formatTimeStamp(data.date) : '';

return (
<ModalLayout onClose={handleClose} labelledBy="title">
<ModalHeader>
<Breadcrumbs label={formattedDate} id="title">
<Crumb>{formattedDate}</Crumb>
</Breadcrumbs>
</ModalHeader>
<ModalBody>
<ActionBody status={status} data={data} formattedDate={formattedDate} />
</ModalBody>
</ModalLayout>
);
};

Modal.propTypes = {
handleClose: PropTypes.func.isRequired,
logId: PropTypes.string.isRequired,
};

export default Modal;
Loading

0 comments on commit de2e6f2

Please sign in to comment.