mirror of
https://github.com/diced/zipline.git
synced 2025-12-05 20:40:12 -08:00
Compare commits
5 Commits
ffbad41994
...
04b27a2dee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b27a2dee | ||
|
|
6f4c3271c1 | ||
|
|
b014f10240 | ||
|
|
d3a417aff0 | ||
|
|
63596d983e |
@@ -6,12 +6,16 @@ import FileTable from './views/FileTable';
|
||||
import Files from './views/Files';
|
||||
import TagsButton from './tags/TagsButton';
|
||||
import PendingFilesButton from './PendingFilesButton';
|
||||
import { IconFileUpload } from '@tabler/icons-react';
|
||||
import { IconFileUpload, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function DashboardFiles() {
|
||||
const view = useViewStore((state) => state.files);
|
||||
|
||||
const [tableEditOpen, setTableEditOpen] = useState(false);
|
||||
const [idSearchOpen, setIdSearchOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group>
|
||||
@@ -28,6 +32,23 @@ export default function DashboardFiles() {
|
||||
<TagsButton />
|
||||
<PendingFilesButton />
|
||||
|
||||
<Tooltip label='Table Options'>
|
||||
<ActionIcon variant='outline' onClick={() => setTableEditOpen((open) => !open)}>
|
||||
<IconTableOptions size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => {
|
||||
setIdSearchOpen((open) => !open);
|
||||
}}
|
||||
>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<GridTableSwitcher type='files' />
|
||||
</Group>
|
||||
|
||||
@@ -38,7 +59,16 @@ export default function DashboardFiles() {
|
||||
<Files />
|
||||
</>
|
||||
) : (
|
||||
<FileTable />
|
||||
<FileTable
|
||||
idSearch={{
|
||||
open: idSearchOpen,
|
||||
setOpen: setIdSearchOpen,
|
||||
}}
|
||||
tableEdit={{
|
||||
open: tableEditOpen,
|
||||
setOpen: setTableEditOpen,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -34,9 +34,7 @@ import {
|
||||
IconDownload,
|
||||
IconExternalLink,
|
||||
IconFile,
|
||||
IconGridPatternFilled,
|
||||
IconStar,
|
||||
IconTableOptions,
|
||||
IconTrashFilled,
|
||||
} from '@tabler/icons-react';
|
||||
import { DataTable } from 'mantine-datatable';
|
||||
@@ -175,12 +173,24 @@ function TagsFilter({
|
||||
);
|
||||
}
|
||||
|
||||
export default function FileTable({ id }: { id?: string }) {
|
||||
export default function FileTable({
|
||||
id,
|
||||
tableEdit,
|
||||
idSearch,
|
||||
}: {
|
||||
id?: string;
|
||||
tableEdit: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
idSearch: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
}) {
|
||||
const clipboard = useClipboard();
|
||||
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
||||
|
||||
const [tableEditOpen, setTableEditOpen] = useState(false);
|
||||
|
||||
const fields = useFileTableSettingsStore((state) => state.fields);
|
||||
|
||||
const { data: folders } = useSWR<Extract<Response['/api/user/folders'], Folder[]>>(
|
||||
@@ -204,7 +214,6 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>('desc');
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
|
||||
const [idSearchOpen, setIdSearchOpen] = useState(false);
|
||||
const [searchField, setSearchField] = useState<'name' | 'originalName' | 'type' | 'tags' | 'id'>('name');
|
||||
const [searchQuery, setSearchQuery] = useReducer(
|
||||
(state: ReducerQuery['state'], action: ReducerQuery['action']) => {
|
||||
@@ -218,13 +227,13 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
const [debouncedQuery, setDebouncedQuery] = useState(searchQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (idSearchOpen) return;
|
||||
if (idSearch.open) return;
|
||||
|
||||
setSearchQuery({
|
||||
field: 'id',
|
||||
query: '',
|
||||
});
|
||||
}, [idSearchOpen]);
|
||||
}, [idSearch.open]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => setDebouncedQuery(searchQuery), 300);
|
||||
@@ -389,33 +398,9 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
file={selectedFile}
|
||||
/>
|
||||
|
||||
<TableEditModal opened={tableEditOpen} onCLose={() => setTableEditOpen(false)} />
|
||||
<TableEditModal opened={tableEdit.open} onCLose={() => tableEdit.setOpen(false)} />
|
||||
|
||||
<Box>
|
||||
<Group>
|
||||
<Tooltip label='Table Options'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => setTableEditOpen((open) => !open)}
|
||||
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
|
||||
>
|
||||
<IconTableOptions size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => {
|
||||
setIdSearchOpen((open) => !open);
|
||||
}}
|
||||
// lol if it works it works :shrug:
|
||||
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
|
||||
>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Collapse in={selectedFiles.length > 0}>
|
||||
<Paper withBorder p='sm' my='sm'>
|
||||
<Text size='sm' c='dimmed' mb='xs'>
|
||||
@@ -500,8 +485,8 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
</Paper>
|
||||
</Collapse>
|
||||
|
||||
<Collapse in={idSearchOpen}>
|
||||
<Paper withBorder p='sm' my='sm'>
|
||||
<Collapse in={idSearch.open}>
|
||||
<Paper withBorder p='sm' mt='sm'>
|
||||
<TextInput
|
||||
placeholder='Search by ID'
|
||||
value={searchQuery.id}
|
||||
@@ -519,6 +504,7 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
|
||||
{/* @ts-ignore */}
|
||||
<DataTable
|
||||
mt='xs'
|
||||
borderRadius='sm'
|
||||
withTableBorder
|
||||
minHeight={200}
|
||||
|
||||
@@ -27,9 +27,9 @@ export default function TwoFAButton() {
|
||||
|
||||
const [totpOpen, setTotpOpen] = useState(false);
|
||||
const {
|
||||
data: twoData,
|
||||
error: twoError,
|
||||
isLoading: twoLoading,
|
||||
data: mfaData,
|
||||
error: mfaError,
|
||||
isLoading: mfaLoading,
|
||||
} = useSWR<Extract<Response['/api/user/mfa/totp'], { secret: string; qrcode: string }>>(
|
||||
totpOpen && !user?.totpSecret ? '/api/user/mfa/totp' : null,
|
||||
null,
|
||||
@@ -51,7 +51,7 @@ export default function TwoFAButton() {
|
||||
'POST',
|
||||
{
|
||||
code: pin,
|
||||
secret: twoData!.secret,
|
||||
secret: mfaData!.secret,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -156,25 +156,20 @@ export default function TwoFAButton() {
|
||||
</Text>
|
||||
|
||||
<Box pos='relative'>
|
||||
{twoLoading && !twoError ? (
|
||||
{mfaLoading && !mfaError ? (
|
||||
<Box w={180} h={180}>
|
||||
<LoadingOverlay visible pos='relative' />
|
||||
</Box>
|
||||
) : (
|
||||
<Center>
|
||||
<Image
|
||||
width={180}
|
||||
height={180}
|
||||
src={twoData?.qrcode}
|
||||
alt={'qr code ' + twoData?.secret}
|
||||
/>
|
||||
<Image h={180} w={180} src={mfaData?.qrcode} alt={'qr code ' + mfaData?.secret} />
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text size='sm' c='dimmed'>
|
||||
If you can't scan the QR code, you can manually enter the following code into your
|
||||
authenticator app: <Code>{twoData?.secret ?? ''}</Code>
|
||||
authenticator app: <Code>{mfaData?.secret ?? ''}</Code>
|
||||
</Text>
|
||||
|
||||
<Text size='sm' c='dimmed'>
|
||||
|
||||
@@ -2,10 +2,11 @@ import { type loader } from '@/client/pages/dashboard/admin/users/[id]/files';
|
||||
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
||||
import { useViewStore } from '@/lib/store/view';
|
||||
import { ActionIcon, Group, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBackUp } from '@tabler/icons-react';
|
||||
import { IconArrowBackUp, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
|
||||
import { Link, useLoaderData } from 'react-router-dom';
|
||||
import FileTable from '../files/views/FileTable';
|
||||
import Files from '../files/views/Files';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function ViewUserFiles() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
@@ -16,6 +17,9 @@ export default function ViewUserFiles() {
|
||||
|
||||
const view = useViewStore((state) => state.files);
|
||||
|
||||
const [tableEditOpen, setTableEditOpen] = useState(false);
|
||||
const [idSearchOpen, setIdSearchOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group>
|
||||
@@ -26,10 +30,41 @@ export default function ViewUserFiles() {
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label='Table Options'>
|
||||
<ActionIcon variant='outline' onClick={() => setTableEditOpen((open) => !open)}>
|
||||
<IconTableOptions size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => {
|
||||
setIdSearchOpen((open) => !open);
|
||||
}}
|
||||
>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<GridTableSwitcher type='files' />
|
||||
</Group>
|
||||
|
||||
{view === 'grid' ? <Files id={user.id} /> : <FileTable id={user.id} />}
|
||||
{view === 'grid' ? (
|
||||
<Files id={user.id} />
|
||||
) : (
|
||||
<FileTable
|
||||
id={user.id}
|
||||
tableEdit={{
|
||||
open: tableEditOpen,
|
||||
setOpen: setTableEditOpen,
|
||||
}}
|
||||
idSearch={{
|
||||
open: idSearchOpen,
|
||||
setOpen: setIdSearchOpen,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,9 +39,12 @@ export async function queryStats(): Promise<MetricData> {
|
||||
});
|
||||
|
||||
for (let i = 0; i !== filesByUser.length; ++i) {
|
||||
const id = filesByUser[i].userId;
|
||||
if (!id) continue;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: filesByUser[i].userId!,
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -49,9 +52,12 @@ export async function queryStats(): Promise<MetricData> {
|
||||
}
|
||||
|
||||
for (let i = 0; i !== urlsByUser.length; ++i) {
|
||||
const id = urlsByUser[i].userId;
|
||||
if (!id) continue;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: urlsByUser[i].userId!,
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export async function filesRoute(
|
||||
|
||||
if (file.User?.view.enabled) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
|
||||
if (file.type.startsWith('text/')) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
|
||||
if (file.password) return res.redirect(`/view/${encodeURIComponent(file.name)}`);
|
||||
|
||||
return rawFileHandler(req, res);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user