Add permissions to ML

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-06-11 16:26:48 +02:00 committed by Alexandre Bodin
parent 170b8a7d18
commit 012941f9ad
13 changed files with 240 additions and 128 deletions

View File

@ -386,36 +386,36 @@ const data = {
}, },
// Upload plugin // Upload plugin
{
action: 'plugins::upload.read',
subject: null,
fields: null,
conditions: [],
},
{
action: 'plugins::upload.assets.create',
subject: null,
fields: null,
conditions: [],
},
// { // {
// action: 'plugins::upload.assets.update', // action: 'plugins::upload.read',
// subject: null,
// fields: null,
// conditions: [],
// },
// {
// action: 'plugins::upload.assets.create',
// subject: null, // subject: null,
// fields: null, // fields: null,
// conditions: [], // conditions: [],
// }, // },
{ {
action: 'plugins::upload.assets.dowload', action: 'plugins::upload.assets.update',
subject: null,
fields: null,
conditions: [],
},
{
action: 'plugins::upload.assets.copy-link',
subject: null, subject: null,
fields: null, fields: null,
conditions: [], conditions: [],
}, },
// {
// action: 'plugins::upload.assets.dowload',
// subject: null,
// fields: null,
// conditions: [],
// },
// {
// action: 'plugins::upload.assets.copy-link',
// subject: null,
// fields: null,
// conditions: [],
// },
{ {
action: 'plugins::upload.settings.read', action: 'plugins::upload.settings.read',
subject: null, subject: null,

View File

@ -15,6 +15,7 @@ import CardControl from '../CardControl';
const BrowseAssets = () => { const BrowseAssets = () => {
const { const {
allowedActions,
allowedTypes, allowedTypes,
count, count,
files, files,
@ -80,7 +81,12 @@ const BrowseAssets = () => {
/* eslint-disable react/jsx-indent */ /* eslint-disable react/jsx-indent */
const renderCardControl = noNavigation const renderCardControl = noNavigation
? null ? null
: id => ( : id => {
if (!allowedActions.canUpdate) {
return null;
}
return (
<CardControl <CardControl
small small
title="edit" title="edit"
@ -92,6 +98,7 @@ const BrowseAssets = () => {
}} }}
/> />
); );
};
/* eslint-enable indent */ /* eslint-enable indent */
/* eslint-enable react/jsx-indent */ /* eslint-enable react/jsx-indent */
@ -113,8 +120,10 @@ const BrowseAssets = () => {
const areResultsEmptyWithSearchOrFilters = isEmpty(files) && (hasSearch || hasFilters); const areResultsEmptyWithSearchOrFilters = isEmpty(files) && (hasSearch || hasFilters);
return ( return (
<>
<Wrapper> <Wrapper>
<Padded top> <Padded top>
{allowedActions.canRead && (
<Flex flexWrap="wrap"> <Flex flexWrap="wrap">
{multiple && ( {multiple && (
<Padded right size="sm"> <Padded right size="sm">
@ -133,9 +142,11 @@ const BrowseAssets = () => {
onClick={handleDeleteFilter} onClick={handleDeleteFilter}
/> />
</Flex> </Flex>
)}
</Padded> </Padded>
{!files || files.length === 0 ? ( {!files || files.length === 0 ? (
<ListEmpty <ListEmpty
canCreate={allowedActions.canCreate}
numberOfRows={2} numberOfRows={2}
onClick={handleGoToUpload} onClick={handleGoToUpload}
hasSearchApplied={areResultsEmptyWithSearchOrFilters} hasSearchApplied={areResultsEmptyWithSearchOrFilters}
@ -164,6 +175,7 @@ const BrowseAssets = () => {
</> </>
)} )}
</Wrapper> </Wrapper>
</>
); );
}; };

View File

@ -38,6 +38,8 @@ import isVideoType from './utils/isVideoType';
const EditForm = forwardRef( const EditForm = forwardRef(
( (
{ {
canCopyLink,
canDownload,
components, components,
fileToEdit, fileToEdit,
isEditingUploadedFile, isEditingUploadedFile,
@ -236,12 +238,14 @@ const EditForm = forwardRef(
/> />
{fileURL && ( {fileURL && (
<> <>
{canDownload && (
<CardControl <CardControl
color="#9EA7B8" color="#9EA7B8"
onClick={handleClickDownload} onClick={handleClickDownload}
type="download" type="download"
title="download" title="download"
/> />
)}
<a <a
title={fileToEdit.fileInfo.name} title={fileToEdit.fileInfo.name}
style={{ display: 'none' }} style={{ display: 'none' }}
@ -249,13 +253,14 @@ const EditForm = forwardRef(
> >
hidden hidden
</a> </a>
{canCopyLink && (
<CopyToClipboard <CopyToClipboard
onCopy={handleCopy} onCopy={handleCopy}
text={prefixFileUrlWithBackendUrl(fileURL)} text={prefixFileUrlWithBackendUrl(fileURL)}
> >
<CardControl color="#9EA7B8" type="link" title="copy-link" /> <CardControl color="#9EA7B8" type="link" title="copy-link" />
</CopyToClipboard> </CopyToClipboard>
)}
</> </>
)} )}
{canCrop && ( {canCrop && (
@ -377,6 +382,8 @@ const EditForm = forwardRef(
); );
EditForm.defaultProps = { EditForm.defaultProps = {
canCopyLink: true,
canDownload: true,
components: { components: {
CheckControl: CardControl, CheckControl: CardControl,
}, },
@ -392,6 +399,8 @@ EditForm.defaultProps = {
}; };
EditForm.propTypes = { EditForm.propTypes = {
canCopyLink: PropTypes.bool,
canDownload: PropTypes.bool,
onAbortUpload: PropTypes.func, onAbortUpload: PropTypes.func,
components: PropTypes.object, components: PropTypes.object,
fileToEdit: PropTypes.object, fileToEdit: PropTypes.object,

View File

@ -20,6 +20,7 @@ const List = ({
smallCards, smallCards,
canSelect, canSelect,
renderCardControl, renderCardControl,
showCheckbox,
}) => { }) => {
const selectedAssets = selectedItems.length; const selectedAssets = selectedItems.length;
@ -57,7 +58,7 @@ const List = ({
> >
{(checked || canSelect) && ( {(checked || canSelect) && (
<> <>
{(checked || isAllowed) && ( {(checked || isAllowed) && showCheckbox && (
<CardControlsWrapper leftAlign className="card-control-wrapper"> <CardControlsWrapper leftAlign className="card-control-wrapper">
<Checkbox <Checkbox
name={`${id}`} name={`${id}`}
@ -92,6 +93,7 @@ List.defaultProps = {
renderCardControl: null, renderCardControl: null,
selectedItems: [], selectedItems: [],
smallCards: false, smallCards: false,
showCheckbox: true,
}; };
List.propTypes = { List.propTypes = {
@ -103,6 +105,7 @@ List.propTypes = {
renderCardControl: PropTypes.func, renderCardControl: PropTypes.func,
selectedItems: PropTypes.array, selectedItems: PropTypes.array,
smallCards: PropTypes.bool, smallCards: PropTypes.bool,
showCheckbox: PropTypes.bool,
}; };
export default List; export default List;

View File

@ -10,7 +10,7 @@ import CardEmpty from '../CardEmpty';
import Wrapper from './Wrapper'; import Wrapper from './Wrapper';
import IntlText from '../IntlText'; import IntlText from '../IntlText';
const ListEmpty = ({ hasSearchApplied, onClick, numberOfRows }) => { const ListEmpty = ({ canCreate, hasSearchApplied, onClick, numberOfRows }) => {
const rows = generateRows(numberOfRows); const rows = generateRows(numberOfRows);
const titleId = hasSearchApplied const titleId = hasSearchApplied
? 'list.assets-empty.title-withSearch' ? 'list.assets-empty.title-withSearch'
@ -34,6 +34,7 @@ const ListEmpty = ({ hasSearchApplied, onClick, numberOfRows }) => {
</div> </div>
); );
})} })}
{canCreate && (
<div className="btn-wrapper"> <div className="btn-wrapper">
<IntlText id={prefixedTitleId} fontSize="lg" fontWeight="semiBold" /> <IntlText id={prefixedTitleId} fontSize="lg" fontWeight="semiBold" />
@ -47,17 +48,20 @@ const ListEmpty = ({ hasSearchApplied, onClick, numberOfRows }) => {
</> </>
)} )}
</div> </div>
)}
</Wrapper> </Wrapper>
); );
}; };
ListEmpty.defaultProps = { ListEmpty.defaultProps = {
canCreate: true,
hasSearchApplied: false, hasSearchApplied: false,
onClick: () => {}, onClick: () => {},
numberOfRows: 3, numberOfRows: 3,
}; };
ListEmpty.propTypes = { ListEmpty.propTypes = {
canCreate: PropTypes.bool,
hasSearchApplied: PropTypes.bool, hasSearchApplied: PropTypes.bool,
onClick: PropTypes.func, onClick: PropTypes.func,
numberOfRows: PropTypes.number, numberOfRows: PropTypes.number,

View File

@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button } from '@buffetjs/core'; import { Button } from '@buffetjs/core';
import { CheckPermissions } from 'strapi-helper-plugin';
import useModalContext from '../../hooks/useModalContext'; import useModalContext from '../../hooks/useModalContext';
import { getTrad } from '../../utils'; import { getTrad } from '../../utils';
import pluginPermissions from '../../permissions';
import BrowseAssets from '../BrowseAssets'; import BrowseAssets from '../BrowseAssets';
import ModalNavWrapper from '../ModalNavWrapper'; import ModalNavWrapper from '../ModalNavWrapper';
import ModalSection from '../ModalSection'; import ModalSection from '../ModalSection';
@ -23,6 +25,7 @@ const ListModal = ({ noNavigation }) => {
const renderUploadModalButton = () => ( const renderUploadModalButton = () => (
<BaselineAlignmentWrapper> <BaselineAlignmentWrapper>
<CheckPermissions permissions={pluginPermissions.create}>
<Button type="button" color="primary" onClick={handleGoToUpload}> <Button type="button" color="primary" onClick={handleGoToUpload}>
<IntlText <IntlText
id={getTrad('modal.upload-list.sub-header.button')} id={getTrad('modal.upload-list.sub-header.button')}
@ -30,6 +33,7 @@ const ListModal = ({ noNavigation }) => {
color="white" color="white"
/> />
</Button> </Button>
</CheckPermissions>
</BaselineAlignmentWrapper> </BaselineAlignmentWrapper>
); );

View File

@ -2,8 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PageFooter, useQuery } from 'strapi-helper-plugin'; import { PageFooter, useQuery } from 'strapi-helper-plugin';
import { generatePageFromStart, generateStartFromPage } from '../../../utils'; import { generatePageFromStart, generateStartFromPage } from '../../../utils';
// TODO import { useAppContext } from '../../../hooks';
// import { useAppContext } from '../../../hooks';
import List from '../../../components/List'; import List from '../../../components/List';
import ListEmpty from '../../../components/ListEmpty'; import ListEmpty from '../../../components/ListEmpty';
import Padded from '../../../components/Padded'; import Padded from '../../../components/Padded';
@ -19,8 +18,9 @@ const HomePageList = ({
onClick, onClick,
}) => { }) => {
const query = useQuery(); const query = useQuery();
// TODO const {
// const { allowedActions } = useAppContext(); allowedActions: { canCreate, canUpdate },
} = useAppContext();
const limit = parseInt(query.get('_limit'), 10) || 10; const limit = parseInt(query.get('_limit'), 10) || 10;
const start = parseInt(query.get('_start'), 10) || 0; const start = parseInt(query.get('_start'), 10) || 0;
@ -48,8 +48,7 @@ const HomePageList = ({
onChange={onCardCheck} onChange={onCardCheck}
onCardClick={onCardClick} onCardClick={onCardClick}
selectedItems={dataToDelete} selectedItems={dataToDelete}
// TODO showCheckbox={canUpdate}
// showCheckbox={allowedActions.canUpdate}
/> />
<Padded left right size="sm"> <Padded left right size="sm">
<Padded left right size="xs"> <Padded left right size="xs">
@ -65,7 +64,13 @@ const HomePageList = ({
); );
} }
return <ListEmpty onClick={onClick} hasSearchApplied={areResultsEmptyWithSettings} />; return (
<ListEmpty
canCreate={canCreate}
onClick={onClick}
hasSearchApplied={areResultsEmptyWithSettings}
/>
);
}; };
HomePageList.defaultProps = { HomePageList.defaultProps = {

View File

@ -27,7 +27,6 @@ import reducer, { initialState } from './reducer';
const HomePage = () => { const HomePage = () => {
const { allowedActions } = useAppContext(); const { allowedActions } = useAppContext();
const { canRead } = allowedActions; const { canRead } = allowedActions;
console.log({ allowedActions });
const { formatMessage, plugins } = useGlobalContext(); const { formatMessage, plugins } = useGlobalContext();
const [, updated_at] = getFileModelTimestamps(plugins); const [, updated_at] = getFileModelTimestamps(plugins);
const [reducerState, dispatch] = useReducer(reducer, initialState, () => const [reducerState, dispatch] = useReducer(reducer, initialState, () =>

View File

@ -9,7 +9,13 @@ import pluginId from '../../pluginId';
import stepper from './stepper'; import stepper from './stepper';
import useModalContext from '../../hooks/useModalContext'; import useModalContext from '../../hooks/useModalContext';
const InputModalStepper = ({ isOpen, onToggle, noNavigation, onInputMediaChange }) => { const InputModalStepper = ({
allowedActions,
isOpen,
onToggle,
noNavigation,
onInputMediaChange,
}) => {
const { emitEvent, formatMessage } = useGlobalContext(); const { emitEvent, formatMessage } = useGlobalContext();
const [shouldDeleteFile, setShouldDeleteFile] = useState(false); const [shouldDeleteFile, setShouldDeleteFile] = useState(false);
const [displayNextButton, setDisplayNextButton] = useState(false); const [displayNextButton, setDisplayNextButton] = useState(false);
@ -308,6 +314,7 @@ const InputModalStepper = ({ isOpen, onToggle, noNavigation, onInputMediaChange
{/* body of the modal */} {/* body of the modal */}
{Component && ( {Component && (
<Component <Component
{...allowedActions}
addFilesToUpload={addFilesToUploadList} addFilesToUpload={addFilesToUploadList}
components={components} components={components}
filesToDownload={filesToDownload} filesToDownload={filesToDownload}
@ -418,11 +425,29 @@ const InputModalStepper = ({ isOpen, onToggle, noNavigation, onInputMediaChange
}; };
InputModalStepper.defaultProps = { InputModalStepper.defaultProps = {
allowedActions: PropTypes.shape({
canCopyLink: PropTypes.bool,
canCreate: PropTypes.bool,
canDownload: PropTypes.bool,
canMain: PropTypes.bool,
canRead: PropTypes.bool,
canSettings: PropTypes.bool,
canUpdate: PropTypes.bool,
}),
noNavigation: false, noNavigation: false,
onToggle: () => {}, onToggle: () => {},
}; };
InputModalStepper.propTypes = { InputModalStepper.propTypes = {
allowedActions: {
canCopyLink: true,
canCreate: true,
canDownload: true,
canMain: true,
canRead: true,
canSettings: true,
canUpdate: true,
},
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
noNavigation: PropTypes.bool, noNavigation: PropTypes.bool,
onInputMediaChange: PropTypes.func.isRequired, onInputMediaChange: PropTypes.func.isRequired,

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend'; import HTML5Backend from 'react-dnd-html5-backend';
import DragLayer from '../../components/DragLayer'; import DragLayer from '../../components/DragLayer';
import { useUserPermissions } from '../../hooks';
import InputModalStepper from './InputModalStepper'; import InputModalStepper from './InputModalStepper';
import InputModalStepperProvider from '../InputModalStepperProvider'; import InputModalStepperProvider from '../InputModalStepperProvider';
@ -20,11 +21,17 @@ const InputModal = ({
step, step,
}) => { }) => {
const singularTypes = allowedTypes.map(type => type.substring(0, type.length - 1)); const singularTypes = allowedTypes.map(type => type.substring(0, type.length - 1));
const { allowedActions, isLoading } = useUserPermissions();
if (isLoading) {
return null;
}
return ( return (
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<DragLayer /> <DragLayer />
<InputModalStepperProvider <InputModalStepperProvider
allowedActions={allowedActions}
onClosed={onClosed} onClosed={onClosed}
initialFilesToUpload={filesToUpload} initialFilesToUpload={filesToUpload}
initialFileToEdit={fileToEdit} initialFileToEdit={fileToEdit}
@ -36,6 +43,7 @@ const InputModal = ({
allowedTypes={singularTypes} allowedTypes={singularTypes}
> >
<InputModalStepper <InputModalStepper
allowedActions={allowedActions}
isOpen={isOpen} isOpen={isOpen}
noNavigation={noNavigation} noNavigation={noNavigation}
onToggle={onToggle} onToggle={onToggle}

View File

@ -15,12 +15,14 @@ import {
formatFilters, formatFilters,
} from '../../utils'; } from '../../utils';
import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager'; import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager';
import init from './init'; import init from './init';
import reducer, { initialState } from './reducer'; import reducer, { initialState } from './reducer';
/* eslint-disable indent */ /* eslint-disable indent */
const InputModalStepperProvider = ({ const InputModalStepperProvider = ({
allowedActions,
allowedTypes, allowedTypes,
children, children,
initialFilesToUpload, initialFilesToUpload,
@ -34,6 +36,7 @@ const InputModalStepperProvider = ({
step, step,
}) => { }) => {
const [formErrors, setFormErrors] = useState(null); const [formErrors, setFormErrors] = useState(null);
const { emitEvent, plugins } = useGlobalContext(); const { emitEvent, plugins } = useGlobalContext();
const [, updated_at] = getFileModelTimestamps(plugins); const [, updated_at] = getFileModelTimestamps(plugins);
const [reducerState, dispatch] = useReducer(reducer, initialState, state => const [reducerState, dispatch] = useReducer(reducer, initialState, state =>
@ -335,12 +338,14 @@ const InputModalStepperProvider = ({
}; };
const fetchMediaLib = async () => { const fetchMediaLib = async () => {
if (allowedActions.canRead) {
const [files, count] = await Promise.all([fetchMediaLibFiles(), fetchMediaLibFilesCount()]); const [files, count] = await Promise.all([fetchMediaLibFiles(), fetchMediaLibFilesCount()]);
dispatch({ dispatch({
type: 'GET_DATA_SUCCEEDED', type: 'GET_DATA_SUCCEEDED',
files, files,
countData: count, countData: count,
}); });
}
}; };
const fetchMediaLibFiles = async () => { const fetchMediaLibFiles = async () => {
@ -455,6 +460,7 @@ const InputModalStepperProvider = ({
<InputModalStepperContext.Provider <InputModalStepperContext.Provider
value={{ value={{
...reducerState, ...reducerState,
allowedActions,
addFilesToUpload, addFilesToUpload,
downloadFiles, downloadFiles,
fetchMediaLib, fetchMediaLib,
@ -495,6 +501,15 @@ const InputModalStepperProvider = ({
}; };
InputModalStepperProvider.propTypes = { InputModalStepperProvider.propTypes = {
allowedActions: PropTypes.shape({
canCopyLink: PropTypes.bool,
canCreate: PropTypes.bool,
canDownload: PropTypes.bool,
canMain: PropTypes.bool,
canRead: PropTypes.bool,
canSettings: PropTypes.bool,
canUpdate: PropTypes.bool,
}),
allowedTypes: PropTypes.arrayOf(PropTypes.string), allowedTypes: PropTypes.arrayOf(PropTypes.string),
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
initialFilesToUpload: PropTypes.object, initialFilesToUpload: PropTypes.object,
@ -509,6 +524,15 @@ InputModalStepperProvider.propTypes = {
}; };
InputModalStepperProvider.defaultProps = { InputModalStepperProvider.defaultProps = {
allowedActions: {
canCopyLink: true,
canCreate: true,
canDownload: true,
canMain: true,
canRead: true,
canSettings: true,
canUpdate: true,
},
initialFileToEdit: null, initialFileToEdit: null,
initialFilesToUpload: null, initialFilesToUpload: null,
isOpen: false, isOpen: false,

View File

@ -13,6 +13,7 @@ import {
import { Button } from '@buffetjs/core'; import { Button } from '@buffetjs/core';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import { getFilesToDownload, getTrad, getYupError, urlSchema } from '../../utils'; import { getFilesToDownload, getTrad, getYupError, urlSchema } from '../../utils';
import { useAppContext } from '../../hooks';
import ModalHeader from '../../components/ModalHeader'; import ModalHeader from '../../components/ModalHeader';
import stepper from './stepper'; import stepper from './stepper';
import init from './init'; import init from './init';
@ -26,6 +27,7 @@ const ModalStepper = ({
onDeleteMedia, onDeleteMedia,
onToggle, onToggle,
}) => { }) => {
const { allowedActions } = useAppContext();
const { emitEvent, formatMessage } = useGlobalContext(); const { emitEvent, formatMessage } = useGlobalContext();
const [isWarningDeleteOpen, setIsWarningDeleteOpen] = useState(false); const [isWarningDeleteOpen, setIsWarningDeleteOpen] = useState(false);
const [shouldDeleteFile, setShouldDeleteFile] = useState(false); const [shouldDeleteFile, setShouldDeleteFile] = useState(false);
@ -479,6 +481,7 @@ const ModalStepper = ({
{/* body of the modal */} {/* body of the modal */}
{Component && ( {Component && (
<Component <Component
{...allowedActions}
onAbortUpload={handleAbortUpload} onAbortUpload={handleAbortUpload}
addFilesToUpload={addFilesToUpload} addFilesToUpload={addFilesToUpload}
fileToEdit={fileToEdit} fileToEdit={fileToEdit}

View File

@ -18,6 +18,14 @@ const pluginPermissions = {
conditions: null, conditions: null,
}, },
], ],
copyLink: [
{
action: 'plugins::upload.assets.copy-link',
subject: null,
fields: null,
conditions: null,
},
],
create: [ create: [
{ {
action: 'plugins::upload.assets.create', action: 'plugins::upload.assets.create',
@ -26,6 +34,14 @@ const pluginPermissions = {
conditions: null, conditions: null,
}, },
], ],
download: [
{
action: 'plugins::upload.assets.download',
subject: null,
fields: null,
conditions: null,
},
],
read: [ read: [
{ action: 'plugins::upload.read', subject: null }, { action: 'plugins::upload.read', subject: null },