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 { capitalize } from 'lib/utils/client';
|
||||||
import { useEffect, useReducer, useState } from 'react';
|
import { useEffect, useReducer, useState } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
import ClearStorage from './ClearStorage';
|
||||||
import Flameshot from './Flameshot';
|
import Flameshot from './Flameshot';
|
||||||
import ShareX from './ShareX';
|
import ShareX from './ShareX';
|
||||||
import { TotpModal } from './TotpModal';
|
import { TotpModal } from './TotpModal';
|
||||||
|
@ -76,10 +77,12 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||||
const [totpOpen, setTotpOpen] = useState(false);
|
const [totpOpen, setTotpOpen] = useState(false);
|
||||||
const [shareXOpen, setShareXOpen] = useState(false);
|
const [shareXOpen, setShareXOpen] = useState(false);
|
||||||
const [flameshotOpen, setFlameshotOpen] = useState(false);
|
const [flameshotOpen, setFlameshotOpen] = useState(false);
|
||||||
|
const [clrStorOpen, setClrStorOpen] = useState(false);
|
||||||
const [exports, setExports] = useState([]);
|
const [exports, setExports] = useState([]);
|
||||||
const [file, setFile] = useState<File>(null);
|
const [file, setFile] = useState<File>(null);
|
||||||
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
||||||
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
||||||
|
const [checked, setCheck] = useState(false);
|
||||||
|
|
||||||
const getDataURL = (f: File): Promise<string> => {
|
const getDataURL = (f: File): Promise<string> => {
|
||||||
return new Promise((res, rej) => {
|
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 handleOauthUnlink = async (provider) => {
|
||||||
const res = await useFetch('/api/auth/oauth', 'DELETE', {
|
const res = await useFetch('/api/auth/oauth', 'DELETE', {
|
||||||
provider,
|
provider,
|
||||||
|
@ -598,7 +549,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||||
<Button size='md' onClick={forceUpdateStats} color='red' rightIcon={<RefreshIcon />}>
|
<Button size='md' onClick={forceUpdateStats} color='red' rightIcon={<RefreshIcon />}>
|
||||||
Force Update Stats
|
Force Update Stats
|
||||||
</Button>
|
</Button>
|
||||||
<Button size='md' onClick={openClearData} color='red' rightIcon={<TrashIcon />}>
|
<Button size='md' onClick={() => setClrStorOpen(true)} color='red' rightIcon={<TrashIcon />}>
|
||||||
Delete all uploads
|
Delete all uploads
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -617,6 +568,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||||
|
|
||||||
<ShareX user={user} open={shareXOpen} setOpen={setShareXOpen} />
|
<ShareX user={user} open={shareXOpen} setOpen={setShareXOpen} />
|
||||||
<Flameshot user={user} open={flameshotOpen} setOpen={setFlameshotOpen} />
|
<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) {
|
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
try {
|
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({});
|
const { count } = await prisma.file.deleteMany({});
|
||||||
logger.info(`User ${user.username} (${user.id}) cleared the database of ${count} files`);
|
logger.info(`User ${user.username} (${user.id}) cleared the database of ${count} files`);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue