UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

446 lines (444 loc) 14.3 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import Box from '@mui/material/Box'; import React, { useCallback, useMemo, useState } from 'react'; import { useStyles } from './styles'; import { DataGrid } from '@mui/x-data-grid'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import Tooltip from '@mui/material/Tooltip'; import IconButton from '@mui/material/IconButton'; import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded'; import { getOffsetLeft, getOffsetTop } from '@mui/material/Popover'; import moment from 'moment-timezone'; import { Button, Typography } from '@mui/material'; import EmptyState from '../EmptyState/EmptyState'; import AuditGridFilterPopover from '../AuditGridFilterPopover'; import { useLocale } from '../../hooks/useLocale'; export const translations = defineMessages({ timestamp: { id: 'auditGrid.timestamp', defaultMessage: 'Timestamp' }, siteName: { id: 'auditGrid.siteName', defaultMessage: 'Project' }, operation: { id: 'auditGrid.operation', defaultMessage: 'Operation' }, targetValue: { id: 'auditGrid.targetValue', defaultMessage: 'Target Value' }, targetType: { id: 'auditGrid.targetType', defaultMessage: 'Target Type' }, username: { id: 'auditGrid.username', defaultMessage: 'Username' }, name: { id: 'auditGrid.name', defaultMessage: 'Name' }, origin: { id: 'auditGrid.origin', defaultMessage: 'Origin' }, parameters: { id: 'auditGrid.parameters', defaultMessage: 'Parameters' } }); export const fieldIdMapping = { operationTimestamp: 'operationTimestamp', siteName: 'siteId', actorId: 'user', origin: 'origin', operation: 'operations', primaryTargetValue: 'target' }; export function AuditGridUI(props) { const { page, auditLogs, onPageChange, onPageSizeChange, onFilterChange, onResetFilter, onResetFilters, filters, sites, users, parametersLookup, operations, origins, timezones, onFetchParameters, hasActiveFilters, siteMode = false } = props; const { classes } = useStyles(); const { formatMessage } = useIntl(); const [anchorPosition, setAnchorPosition] = useState(null); const [openedFilter, setOpenedFilter] = useState(); const [timezone, setTimezone] = useState(moment.tz.guess()); const [sortModel, setSortModel] = useState([{ field: 'operationTimestamp', sort: 'desc' }]); const localeBranch = useLocale(); const onFilterSelected = (props) => { if (props.open && anchorPosition === null) { setTimeout(() => { setOpenedFilter(props.currentColumn.field); const element = document.getElementById(props.labelledby); const anchorRect = element.getBoundingClientRect(); const top = anchorRect.top + getOffsetTop(anchorRect, 'top'); const left = anchorRect.left + getOffsetLeft(anchorRect, 'left'); setAnchorPosition({ top, left }); }); } return null; }; const onGetParameters = useCallback( (params) => { onFetchParameters(params.id); }, [onFetchParameters] ); const onTimestampSortChanges = (model) => { const newSort = model.find((m) => m.field === 'operationTimestamp').sort; const sort = sortModel.find((m) => m.field === 'operationTimestamp').sort; if (newSort !== sort) { setSortModel(model); if (newSort === 'asc') { onFilterChange({ id: 'order', value: newSort.toUpperCase() }); } else { onFilterChange({ id: 'order', value: undefined }); } } }; const onTimezoneSelected = (timezone) => { setTimezone(timezone); }; const onPopoverFilterChanges = (id, value) => { onFilterChange({ id, value: value === 'all' ? undefined : value }); }; const columns = useMemo( () => [ { field: 'operationTimestamp', headerName: formatMessage(translations.timestamp), width: 200, cellClassName: classes.cellRoot, renderCell: (params) => { const date = new Intl.DateTimeFormat( localeBranch.localeCode, Object.assign(Object.assign({}, localeBranch.dateTimeFormatOptions), { timeZone: timezone }) ).format(new Date(params.value)); return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: date === null || date === void 0 ? void 0 : date.toString() }, date ); }, headerClassName: (filters['dateFrom'] || filters['dateTo']) && classes.activeFilter }, { field: 'siteName', headerName: formatMessage(translations.siteName), width: 150, sortable: false, cellClassName: classes.cellRoot, headerClassName: filters[fieldIdMapping['siteName']] && classes.activeFilter, hide: siteMode, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'actorId', headerName: formatMessage(translations.username), width: 150, sortable: false, cellClassName: classes.cellRoot, headerClassName: filters[fieldIdMapping['actorId']] && classes.activeFilter, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'operation', headerName: formatMessage(translations.operation), width: 150, sortable: false, cellClassName: classes.cellRoot, headerClassName: filters[fieldIdMapping['operation']] && classes.activeFilter, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'primaryTargetValue', headerName: formatMessage(translations.targetValue), width: 300, sortable: false, cellClassName: classes.cellRoot, headerClassName: filters[fieldIdMapping['primaryTargetValue']] && classes.activeFilter, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'primaryTargetType', headerName: formatMessage(translations.targetType), width: 150, disableColumnMenu: true, sortable: false, cellClassName: classes.cellRoot, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'actorDetails', headerName: formatMessage(translations.name), width: 100, disableColumnMenu: true, sortable: false, cellClassName: classes.cellRoot, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'origin', headerName: formatMessage(translations.origin), width: 100, sortable: false, cellClassName: classes.cellRoot, headerClassName: filters[fieldIdMapping['origin']] && classes.activeFilter, renderCell: (params) => { var _a; return React.createElement( Typography, { variant: 'body2', className: classes.ellipsis, title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString() }, params.value ); } }, { field: 'parameters', headerName: formatMessage(translations.parameters), width: 105, disableColumnMenu: true, renderCell: (params) => { var _a; return parametersLookup[params.id] === undefined || ((_a = parametersLookup[params.id]) === null || _a === void 0 ? void 0 : _a.length) ? React.createElement( Tooltip, { title: React.createElement(FormattedMessage, { id: 'auditGrid.showParameters', defaultMessage: 'Show parameters' }) }, React.createElement( IconButton, { onClick: () => onGetParameters(params), size: 'large' }, React.createElement(VisibilityRoundedIcon, null) ) ) : React.createElement( Typography, { color: 'textSecondary', variant: 'caption' }, '(', React.createElement(FormattedMessage, { id: 'auditGrid.noParameters', defaultMessage: 'No parameters' }), ')' ); }, sortable: false, cellClassName: classes.cellRoot } ], [ classes.activeFilter, classes.cellRoot, classes.ellipsis, filters, formatMessage, localeBranch.dateTimeFormatOptions, localeBranch.localeCode, onGetParameters, parametersLookup, siteMode, timezone ] ); return React.createElement( Box, { display: 'flex' }, React.createElement(DataGrid, { sortingOrder: ['desc', 'asc'], sortModel: sortModel, sortingMode: 'server', autoHeight: Boolean(auditLogs.length), disableColumnFilter: true, className: classes.gridRoot, components: { ColumnMenu: onFilterSelected, NoRowsOverlay: () => React.createElement( Box, null, React.createElement( EmptyState, { styles: { root: { position: 'relative', zIndex: 1, paddingTop: 10, paddingBottom: 10, margin: 0 } }, title: React.createElement(FormattedMessage, { id: 'auditGrid.emptyStateMessage', defaultMessage: 'No Logs Found' }) }, hasActiveFilters && React.createElement( Button, { variant: 'text', color: 'primary', onClick: onResetFilters }, React.createElement(FormattedMessage, { id: 'auditGrid.clearFilters', defaultMessage: 'Clear filters' }) ) ) ) }, disableSelectionOnClick: true, disableColumnSelector: true, rows: auditLogs, columns: columns, page: page, pageSize: auditLogs.limit, onSortModelChange: onTimestampSortChanges, onPageSizeChange: onPageSizeChange, rowsPerPageOptions: [5, 10, 15], paginationMode: 'server', onPageChange: onPageChange, rowCount: auditLogs.total }), React.createElement(AuditGridFilterPopover, { open: Boolean(anchorPosition), anchorPosition: anchorPosition, onClose: () => setAnchorPosition(null), filterId: fieldIdMapping[openedFilter], onResetFilter: onResetFilter, onFilterChange: onPopoverFilterChanges, value: filters[fieldIdMapping[openedFilter]], dateFrom: filters['dateFrom'], dateTo: filters['dateTo'], timezone: timezone, onTimezoneSelected: onTimezoneSelected, options: { users, sites, operations, origins, timezones } }) ); } export default AuditGridUI;