Update permissions handling for file manager; ensure errors are shown
This commit is contained in:
parent
9347ee8d78
commit
9b4f2deb78
|
@ -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,30 +119,37 @@ 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'}
|
||||||
>
|
>
|
||||||
<div
|
<Can action={'file.update'}>
|
||||||
onClick={() => setModal('rename')}
|
<div
|
||||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
onClick={() => setModal('rename')}
|
||||||
>
|
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||||
<FontAwesomeIcon icon={faPencilAlt} className={'text-xs'}/>
|
>
|
||||||
<span className={'ml-2'}>Rename</span>
|
<FontAwesomeIcon icon={faPencilAlt} className={'text-xs'}/>
|
||||||
</div>
|
<span className={'ml-2'}>Rename</span>
|
||||||
<div
|
</div>
|
||||||
onClick={() => setModal('move')}
|
<div
|
||||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
onClick={() => setModal('move')}
|
||||||
>
|
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||||
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
|
>
|
||||||
<span className={'ml-2'}>Move</span>
|
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
|
||||||
</div>
|
<span className={'ml-2'}>Move</span>
|
||||||
<div
|
</div>
|
||||||
onClick={() => doCopy()}
|
</Can>
|
||||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
<Can action={'file.create'}>
|
||||||
>
|
<div
|
||||||
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
|
onClick={() => doCopy()}
|
||||||
<span className={'ml-2'}>Copy</span>
|
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||||
</div>
|
>
|
||||||
|
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
|
||||||
|
<span className={'ml-2'}>Copy</span>
|
||||||
|
</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,13 +157,15 @@ 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>
|
||||||
<div
|
<Can action={'file.delete'}>
|
||||||
onClick={() => doDeletion()}
|
<div
|
||||||
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
|
onClick={() => doDeletion()}
|
||||||
>
|
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
|
||||||
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
|
>
|
||||||
<span className={'ml-2'}>Delete</span>
|
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
|
||||||
</div>
|
<span className={'ml-2'}>Delete</span>
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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,10 +47,10 @@ 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(() => {
|
||||||
if (name) {
|
if (name) {
|
||||||
history.push(`/server/${id}/files/edit#/${name}`);
|
history.push(`/server/${id}/files/edit#/${name}`);
|
||||||
|
@ -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' ?
|
||||||
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
|
<Can action={'file.update'}>
|
||||||
Save Content
|
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
|
||||||
</button>
|
Save Content
|
||||||
|
</button>
|
||||||
|
</Can>
|
||||||
:
|
:
|
||||||
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
|
<Can action={'file.create'}>
|
||||||
Create File
|
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
|
||||||
</button>
|
Create File
|
||||||
|
</button>
|
||||||
|
</Can>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
<div className={'flex justify-end mt-8'}>
|
<Can action={'file.create'}>
|
||||||
<NewDirectoryButton/>
|
<div className={'flex justify-end mt-8'}>
|
||||||
<Link to={`/server/${id}/files/new${window.location.hash}`} className={'btn btn-sm btn-primary'}>
|
<NewDirectoryButton/>
|
||||||
New File
|
<Link
|
||||||
</Link>
|
to={`/server/${id}/files/new${window.location.hash}`}
|
||||||
</div>
|
className={'btn btn-sm btn-primary'}
|
||||||
|
>
|
||||||
|
New File
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
Loading…
Reference in New Issue