Skip to content

Commit 9b4f2de

Browse files
committed
Update permissions handling for file manager; ensure errors are shown
1 parent 9347ee8 commit 9b4f2de

File tree

3 files changed

+78
-51
lines changed

3 files changed

+78
-51
lines changed

resources/scripts/components/server/files/FileDropdownMenu.tsx

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import { join } from 'path';
1313
import deleteFile from '@/api/server/files/deleteFile';
1414
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
1515
import copyFile from '@/api/server/files/copyFile';
16-
import http, { httpErrorToHuman } from '@/api/http';
16+
import { httpErrorToHuman } from '@/api/http';
17+
import Can from '@/components/elements/Can';
1718

1819
type ModalType = 'rename' | 'move';
1920

@@ -118,44 +119,53 @@ export default ({ uuid }: { uuid: string }) => {
118119
<CSSTransition timeout={250} in={menuVisible} unmountOnExit={true} classNames={'fade'}>
119120
<div
120121
ref={menu}
121-
onClick={e => { e.stopPropagation(); setMenuVisible(false); }}
122+
onClick={e => {
123+
e.stopPropagation();
124+
setMenuVisible(false);
125+
}}
122126
className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'}
123127
>
124-
<div
125-
onClick={() => setModal('rename')}
126-
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
127-
>
128-
<FontAwesomeIcon icon={faPencilAlt} className={'text-xs'}/>
129-
<span className={'ml-2'}>Rename</span>
130-
</div>
131-
<div
132-
onClick={() => setModal('move')}
133-
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
134-
>
135-
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
136-
<span className={'ml-2'}>Move</span>
137-
</div>
138-
<div
139-
onClick={() => doCopy()}
140-
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
141-
>
142-
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
143-
<span className={'ml-2'}>Copy</span>
144-
</div>
128+
<Can action={'file.update'}>
129+
<div
130+
onClick={() => setModal('rename')}
131+
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
132+
>
133+
<FontAwesomeIcon icon={faPencilAlt} className={'text-xs'}/>
134+
<span className={'ml-2'}>Rename</span>
135+
</div>
136+
<div
137+
onClick={() => setModal('move')}
138+
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
139+
>
140+
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
141+
<span className={'ml-2'}>Move</span>
142+
</div>
143+
</Can>
144+
<Can action={'file.create'}>
145+
<div
146+
onClick={() => doCopy()}
147+
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
148+
>
149+
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
150+
<span className={'ml-2'}>Copy</span>
151+
</div>
152+
</Can>
145153
<div
146154
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
147155
onClick={() => doDownload()}
148156
>
149157
<FontAwesomeIcon icon={faFileDownload} className={'text-xs'}/>
150158
<span className={'ml-2'}>Download</span>
151159
</div>
152-
<div
153-
onClick={() => doDeletion()}
154-
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
155-
>
156-
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
157-
<span className={'ml-2'}>Delete</span>
158-
</div>
160+
<Can action={'file.delete'}>
161+
<div
162+
onClick={() => doDeletion()}
163+
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
164+
>
165+
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
166+
<span className={'ml-2'}>Delete</span>
167+
</div>
168+
</Can>
159169
</div>
160170
</CSSTransition>
161171
</div>

resources/scripts/components/server/files/FileEditContainer.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import React, { lazy, useEffect, useState } from 'react';
22
import { ServerContext } from '@/state/server';
33
import getFileContents from '@/api/server/files/getFileContents';
44
import useRouter from 'use-react-router';
5-
import { Actions, useStoreState } from 'easy-peasy';
5+
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
66
import { ApplicationStore } from '@/state';
77
import { httpErrorToHuman } from '@/api/http';
88
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
99
import saveFileContents from '@/api/server/files/saveFileContents';
1010
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
1111
import { useParams } from 'react-router';
1212
import FileNameModal from '@/components/server/files/FileNameModal';
13+
import Can from '@/components/elements/Can';
14+
import FlashMessageRender from '@/components/FlashMessageRender';
1315

1416
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
1517

@@ -21,15 +23,20 @@ export default () => {
2123
const [ modalVisible, setModalVisible ] = useState(false);
2224

2325
const { id, uuid } = ServerContext.useStoreState(state => state.server.data!);
24-
const addError = useStoreState((state: Actions<ApplicationStore>) => state.flashes.addError);
26+
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
2527

2628
let fetchFileContent: null | (() => Promise<string>) = null;
2729

2830
if (action !== 'new') {
2931
useEffect(() => {
32+
setLoading(true);
33+
clearFlashes('files:view');
3034
getFileContents(uuid, hash.replace(/^#/, ''))
3135
.then(setContent)
32-
.catch(error => console.error(error))
36+
.catch(error => {
37+
console.error(error);
38+
addError({ key: 'files:view', message: httpErrorToHuman(error) });
39+
})
3340
.then(() => setLoading(false));
3441
}, [ uuid, hash ]);
3542
}
@@ -40,10 +47,10 @@ export default () => {
4047
}
4148

4249
setLoading(true);
43-
fetchFileContent()
44-
.then(content => {
45-
return saveFileContents(uuid, name || hash.replace(/^#/, ''), content);
46-
})
50+
clearFlashes('files:view');
51+
fetchFileContent().then(content => {
52+
return saveFileContents(uuid, name || hash.replace(/^#/, ''), content);
53+
})
4754
.then(() => {
4855
if (name) {
4956
history.push(`/server/${id}/files/edit#/${name}`);
@@ -54,13 +61,14 @@ export default () => {
5461
})
5562
.catch(error => {
5663
console.error(error);
57-
addError({ message: httpErrorToHuman(error), key: 'files' });
64+
addError({ message: httpErrorToHuman(error), key: 'files:view' });
5865
})
5966
.then(() => setLoading(false));
6067
};
6168

6269
return (
6370
<div className={'mt-10 mb-4'}>
71+
<FlashMessageRender byKey={'files:view'} className={'mb-4'}/>
6472
<FileManagerBreadcrumbs withinFileEditor={true} isNewFile={action !== 'edit'}/>
6573
<FileNameModal
6674
visible={modalVisible}
@@ -83,13 +91,17 @@ export default () => {
8391
</div>
8492
<div className={'flex justify-end mt-4'}>
8593
{action === 'edit' ?
86-
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
87-
Save Content
88-
</button>
94+
<Can action={'file.update'}>
95+
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
96+
Save Content
97+
</button>
98+
</Can>
8999
:
90-
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
91-
Create File
92-
</button>
100+
<Can action={'file.create'}>
101+
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
102+
Create File
103+
</button>
104+
</Can>
93105
}
94106
</div>
95107
</div>

resources/scripts/components/server/files/FileManagerContainer.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcr
1111
import { FileObject } from '@/api/server/files/loadDirectory';
1212
import NewDirectoryButton from '@/components/server/files/NewDirectoryButton';
1313
import { Link } from 'react-router-dom';
14+
import Can from '@/components/elements/Can';
1415

1516
const sortFiles = (files: FileObject[]): FileObject[] => {
1617
return files.sort((a, b) => a.name.localeCompare(b.name))
@@ -34,7 +35,6 @@ export default () => {
3435
console.error(error.message, { error });
3536
addError({ message: httpErrorToHuman(error), key: 'files' });
3637
});
37-
// eslint-disable-next-line react-hooks/exhaustive-deps
3838
}, [ directory ]);
3939

4040
return (
@@ -78,12 +78,17 @@ export default () => {
7878
</React.Fragment>
7979
</CSSTransition>
8080
}
81-
<div className={'flex justify-end mt-8'}>
82-
<NewDirectoryButton/>
83-
<Link to={`/server/${id}/files/new${window.location.hash}`} className={'btn btn-sm btn-primary'}>
84-
New File
85-
</Link>
86-
</div>
81+
<Can action={'file.create'}>
82+
<div className={'flex justify-end mt-8'}>
83+
<NewDirectoryButton/>
84+
<Link
85+
to={`/server/${id}/files/new${window.location.hash}`}
86+
className={'btn btn-sm btn-primary'}
87+
>
88+
New File
89+
</Link>
90+
</div>
91+
</Can>
8792
</React.Fragment>
8893
}
8994
</React.Fragment>

0 commit comments

Comments
 (0)