feat: clearing orphaned files (#303)
This commit is contained in:
parent
2d69cd580a
commit
95a1c7f92c
3 changed files with 103 additions and 53 deletions
88
src/components/pages/Manage/ClearStorage.tsx
Normal file
88
src/components/pages/Manage/ClearStorage.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { Button, Checkbox, Group, Modal, Text, Title } from '@mantine/core';
|
||||
import { closeAllModals, openConfirmModal } from '@mantine/modals';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import { CheckIcon, CrossIcon } from 'components/icons';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
|
||||
export default function ClearStorage({ open, setOpen, check, setCheck }) {
|
||||
const handleDelete = async (datasource: boolean, orphaned?: boolean) => {
|
||||
showNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Clearing...',
|
||||
message: '',
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
});
|
||||
|
||||
const res = await useFetch('/api/admin/clear', 'POST', { datasource, orphaned });
|
||||
|
||||
if (res.error) {
|
||||
updateNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Error while clearing uploads',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
updateNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Successfully cleared uploads',
|
||||
message: '',
|
||||
color: 'green',
|
||||
icon: <CheckIcon />,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={open}
|
||||
onClose={() => setOpen(false)}
|
||||
title={<Title size='sm'>Are you sure you want to clear all uploads in the database?</Title>}
|
||||
>
|
||||
<Checkbox
|
||||
id='orphanedFiles'
|
||||
label='Clear only orphaned files?'
|
||||
description='Orphaned files are not owned by anyone. They can't be seen the dashboard by anyone.'
|
||||
checked={check}
|
||||
onChange={(e) => setCheck(e.currentTarget.checked)}
|
||||
/>
|
||||
<Group position='right' mt='md'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(() => false);
|
||||
}}
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
openConfirmModal({
|
||||
title: 'Do you want to clear storage too?',
|
||||
labels: { confirm: 'Yes', cancel: check ? 'Ok' : 'No' },
|
||||
children: check && (
|
||||
<Text size='sm' color='gray'>
|
||||
Due to clearing orphaned files, storage clearing will be unavailable.
|
||||
</Text>
|
||||
),
|
||||
confirmProps: { disabled: check },
|
||||
onConfirm: () => {
|
||||
closeAllModals();
|
||||
handleDelete(true);
|
||||
},
|
||||
onCancel: () => {
|
||||
closeAllModals();
|
||||
handleDelete(false, check);
|
||||
},
|
||||
onClose: () => setCheck(false),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -42,6 +42,7 @@ import { bytesToHuman } from 'lib/utils/bytes';
|
|||
import { capitalize } from 'lib/utils/client';
|
||||
import { useEffect, useReducer, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import ClearStorage from './ClearStorage';
|
||||
import Flameshot from './Flameshot';
|
||||
import ShareX from './ShareX';
|
||||
import { TotpModal } from './TotpModal';
|
||||
|
@ -76,10 +77,12 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
const [totpOpen, setTotpOpen] = useState(false);
|
||||
const [shareXOpen, setShareXOpen] = useState(false);
|
||||
const [flameshotOpen, setFlameshotOpen] = useState(false);
|
||||
const [clrStorOpen, setClrStorOpen] = useState(false);
|
||||
const [exports, setExports] = useState([]);
|
||||
const [file, setFile] = useState<File>(null);
|
||||
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
||||
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
||||
const [checked, setCheck] = useState(false);
|
||||
|
||||
const getDataURL = (f: File): Promise<string> => {
|
||||
return new Promise((res, rej) => {
|
||||
|
@ -312,58 +315,6 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
}
|
||||
};
|
||||
|
||||
const openClearData = () => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Are you sure you want to clear all uploads in the database?',
|
||||
closeOnConfirm: false,
|
||||
labels: { confirm: 'Yes', cancel: 'No' },
|
||||
onConfirm: () => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Do you want to clear storage too?',
|
||||
labels: { confirm: 'Yes', cancel: 'No' },
|
||||
onConfirm: () => {
|
||||
handleClearData(true);
|
||||
modals.closeAll();
|
||||
},
|
||||
onCancel: () => {
|
||||
handleClearData(false);
|
||||
modals.closeAll();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearData = async (datasource?: boolean) => {
|
||||
showNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Clearing...',
|
||||
message: '',
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
});
|
||||
|
||||
const res = await useFetch('/api/admin/clear', 'POST', { datasource });
|
||||
|
||||
if (res.error) {
|
||||
updateNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Error while clearing uploads',
|
||||
message: res.error,
|
||||
color: 'red',
|
||||
icon: <CrossIcon />,
|
||||
});
|
||||
} else {
|
||||
updateNotification({
|
||||
id: 'clear-uploads',
|
||||
title: 'Successfully cleared uploads',
|
||||
message: '',
|
||||
color: 'green',
|
||||
icon: <CheckIcon />,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOauthUnlink = async (provider) => {
|
||||
const res = await useFetch('/api/auth/oauth', 'DELETE', {
|
||||
provider,
|
||||
|
@ -598,7 +549,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
<Button size='md' onClick={forceUpdateStats} color='red' rightIcon={<RefreshIcon />}>
|
||||
Force Update Stats
|
||||
</Button>
|
||||
<Button size='md' onClick={openClearData} color='red' rightIcon={<TrashIcon />}>
|
||||
<Button size='md' onClick={() => setClrStorOpen(true)} color='red' rightIcon={<TrashIcon />}>
|
||||
Delete all uploads
|
||||
</Button>
|
||||
</Group>
|
||||
|
@ -617,6 +568,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
|
||||
<ShareX user={user} open={shareXOpen} setOpen={setShareXOpen} />
|
||||
<Flameshot user={user} open={flameshotOpen} setOpen={setFlameshotOpen} />
|
||||
<ClearStorage open={clrStorOpen} setOpen={setClrStorOpen} check={checked} setCheck={setCheck} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,16 @@ const logger = Logger.get('admin');
|
|||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
try {
|
||||
const { datasource, orphaned } = req.body;
|
||||
if (orphaned) {
|
||||
const { count } = await prisma.file.deleteMany({
|
||||
where: {
|
||||
userId: null,
|
||||
},
|
||||
});
|
||||
logger.info(`User ${user.username} (${user.id}) cleared the database of ${count} orphaned files`);
|
||||
return res.json({ message: 'cleared storage (orphaned only)' });
|
||||
}
|
||||
const { count } = await prisma.file.deleteMany({});
|
||||
logger.info(`User ${user.username} (${user.id}) cleared the database of ${count} files`);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue