Update permissions handling for file manager; ensure errors are shown

This commit is contained in:
Dane Everitt 2020-03-29 21:42:02 -07:00
parent 9347ee8d78
commit 9b4f2deb78
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
3 changed files with 78 additions and 51 deletions

View File

@ -13,7 +13,8 @@ import { join } from 'path';
import deleteFile from '@/api/server/files/deleteFile'; import deleteFile from '@/api/server/files/deleteFile';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import copyFile from '@/api/server/files/copyFile'; import copyFile from '@/api/server/files/copyFile';
import http, { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import Can from '@/components/elements/Can';
type ModalType = 'rename' | 'move'; type ModalType = 'rename' | 'move';
@ -118,9 +119,13 @@ export default ({ uuid }: { uuid: string }) => {
<CSSTransition timeout={250} in={menuVisible} unmountOnExit={true} classNames={'fade'}> <CSSTransition timeout={250} in={menuVisible} unmountOnExit={true} classNames={'fade'}>
<div <div
ref={menu} ref={menu}
onClick={e => { e.stopPropagation(); setMenuVisible(false); }} onClick={e => {
e.stopPropagation();
setMenuVisible(false);
}}
className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'} className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'}
> >
<Can action={'file.update'}>
<div <div
onClick={() => setModal('rename')} onClick={() => setModal('rename')}
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
@ -135,6 +140,8 @@ export default ({ uuid }: { uuid: string }) => {
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/> <FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
<span className={'ml-2'}>Move</span> <span className={'ml-2'}>Move</span>
</div> </div>
</Can>
<Can action={'file.create'}>
<div <div
onClick={() => doCopy()} onClick={() => doCopy()}
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
@ -142,6 +149,7 @@ export default ({ uuid }: { uuid: string }) => {
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/> <FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
<span className={'ml-2'}>Copy</span> <span className={'ml-2'}>Copy</span>
</div> </div>
</Can>
<div <div
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'} className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
onClick={() => doDownload()} onClick={() => doDownload()}
@ -149,6 +157,7 @@ export default ({ uuid }: { uuid: string }) => {
<FontAwesomeIcon icon={faFileDownload} className={'text-xs'}/> <FontAwesomeIcon icon={faFileDownload} className={'text-xs'}/>
<span className={'ml-2'}>Download</span> <span className={'ml-2'}>Download</span>
</div> </div>
<Can action={'file.delete'}>
<div <div
onClick={() => doDeletion()} onClick={() => doDeletion()}
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'} className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
@ -156,6 +165,7 @@ export default ({ uuid }: { uuid: string }) => {
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/> <FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
<span className={'ml-2'}>Delete</span> <span className={'ml-2'}>Delete</span>
</div> </div>
</Can>
</div> </div>
</CSSTransition> </CSSTransition>
</div> </div>

View File

@ -2,7 +2,7 @@ import React, { lazy, useEffect, useState } from 'react';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import getFileContents from '@/api/server/files/getFileContents'; import getFileContents from '@/api/server/files/getFileContents';
import useRouter from 'use-react-router'; import useRouter from 'use-react-router';
import { Actions, useStoreState } from 'easy-peasy'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
@ -10,6 +10,8 @@ import saveFileContents from '@/api/server/files/saveFileContents';
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import FileNameModal from '@/components/server/files/FileNameModal'; import FileNameModal from '@/components/server/files/FileNameModal';
import Can from '@/components/elements/Can';
import FlashMessageRender from '@/components/FlashMessageRender';
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor')); const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
@ -21,15 +23,20 @@ export default () => {
const [ modalVisible, setModalVisible ] = useState(false); const [ modalVisible, setModalVisible ] = useState(false);
const { id, uuid } = ServerContext.useStoreState(state => state.server.data!); const { id, uuid } = ServerContext.useStoreState(state => state.server.data!);
const addError = useStoreState((state: Actions<ApplicationStore>) => state.flashes.addError); const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
let fetchFileContent: null | (() => Promise<string>) = null; let fetchFileContent: null | (() => Promise<string>) = null;
if (action !== 'new') { if (action !== 'new') {
useEffect(() => { useEffect(() => {
setLoading(true);
clearFlashes('files:view');
getFileContents(uuid, hash.replace(/^#/, '')) getFileContents(uuid, hash.replace(/^#/, ''))
.then(setContent) .then(setContent)
.catch(error => console.error(error)) .catch(error => {
console.error(error);
addError({ key: 'files:view', message: httpErrorToHuman(error) });
})
.then(() => setLoading(false)); .then(() => setLoading(false));
}, [ uuid, hash ]); }, [ uuid, hash ]);
} }
@ -40,8 +47,8 @@ export default () => {
} }
setLoading(true); setLoading(true);
fetchFileContent() clearFlashes('files:view');
.then(content => { fetchFileContent().then(content => {
return saveFileContents(uuid, name || hash.replace(/^#/, ''), content); return saveFileContents(uuid, name || hash.replace(/^#/, ''), content);
}) })
.then(() => { .then(() => {
@ -54,13 +61,14 @@ export default () => {
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
addError({ message: httpErrorToHuman(error), key: 'files' }); addError({ message: httpErrorToHuman(error), key: 'files:view' });
}) })
.then(() => setLoading(false)); .then(() => setLoading(false));
}; };
return ( return (
<div className={'mt-10 mb-4'}> <div className={'mt-10 mb-4'}>
<FlashMessageRender byKey={'files:view'} className={'mb-4'}/>
<FileManagerBreadcrumbs withinFileEditor={true} isNewFile={action !== 'edit'}/> <FileManagerBreadcrumbs withinFileEditor={true} isNewFile={action !== 'edit'}/>
<FileNameModal <FileNameModal
visible={modalVisible} visible={modalVisible}
@ -83,13 +91,17 @@ export default () => {
</div> </div>
<div className={'flex justify-end mt-4'}> <div className={'flex justify-end mt-4'}>
{action === 'edit' ? {action === 'edit' ?
<Can action={'file.update'}>
<button className={'btn btn-primary btn-sm'} onClick={() => save()}> <button className={'btn btn-primary btn-sm'} onClick={() => save()}>
Save Content Save Content
</button> </button>
</Can>
: :
<Can action={'file.create'}>
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}> <button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
Create File Create File
</button> </button>
</Can>
} }
</div> </div>
</div> </div>

View File

@ -11,6 +11,7 @@ import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcr
import { FileObject } from '@/api/server/files/loadDirectory'; import { FileObject } from '@/api/server/files/loadDirectory';
import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; import NewDirectoryButton from '@/components/server/files/NewDirectoryButton';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Can from '@/components/elements/Can';
const sortFiles = (files: FileObject[]): FileObject[] => { const sortFiles = (files: FileObject[]): FileObject[] => {
return files.sort((a, b) => a.name.localeCompare(b.name)) return files.sort((a, b) => a.name.localeCompare(b.name))
@ -34,7 +35,6 @@ export default () => {
console.error(error.message, { error }); console.error(error.message, { error });
addError({ message: httpErrorToHuman(error), key: 'files' }); addError({ message: httpErrorToHuman(error), key: 'files' });
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ directory ]); }, [ directory ]);
return ( return (
@ -78,12 +78,17 @@ export default () => {
</React.Fragment> </React.Fragment>
</CSSTransition> </CSSTransition>
} }
<Can action={'file.create'}>
<div className={'flex justify-end mt-8'}> <div className={'flex justify-end mt-8'}>
<NewDirectoryButton/> <NewDirectoryButton/>
<Link to={`/server/${id}/files/new${window.location.hash}`} className={'btn btn-sm btn-primary'}> <Link
to={`/server/${id}/files/new${window.location.hash}`}
className={'btn btn-sm btn-primary'}
>
New File New File
</Link> </Link>
</div> </div>
</Can>
</React.Fragment> </React.Fragment>
} }
</React.Fragment> </React.Fragment>