Skip to content

Commit

Permalink
feat: add download functionality for board configuration files (#2111)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meierschlumpf committed Aug 24, 2024
1 parent cc240f4 commit a879358
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 22 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@trpc/server": "^10.37.1",
"@types/bcryptjs": "^2.4.2",
"@vitejs/plugin-react": "^4.0.0",
"adm-zip": "^0.5.15",
"axios": "^1.0.0",
"bcryptjs": "^2.4.3",
"better-sqlite3": "^8.6.0",
Expand Down Expand Up @@ -120,6 +121,7 @@
"@next/eslint-plugin-next": "^13.4.5",
"@testing-library/react": "^14.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/adm-zip": "^0.5.5",
"@types/better-sqlite3": "^7.6.5",
"@types/cookies": "^0.7.7",
"@types/dockerode": "^3.3.9",
Expand Down
34 changes: 34 additions & 0 deletions src/pages/api/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import AdmZip from 'adm-zip';
import fs from 'fs';
import { NextApiRequest, NextApiResponse } from 'next';
import { getServerAuthSession } from '~/server/auth';
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerAuthSession({ req, res });
if (!session) {
return res.status(401).end();
}

if (!session.user.isAdmin) {
return res.status(403).end();
}

const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));

const zip = new AdmZip();

for (const file of files) {
const data = await getFrontendConfig(file.replace('.json', ''));
const content = JSON.stringify(data, null, 2);
zip.addFile(file, Buffer.from(content, 'utf-8'));
}

const zipBuffer = zip.toBuffer();
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', 'attachment; filename=board-configs.zip');
res.setHeader('Content-Length', zipBuffer.length.toString());
res.status(200).end(zipBuffer);
};

export default handler;
68 changes: 46 additions & 22 deletions src/pages/manage/boards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import {
Title,
} from '@mantine/core';
import { useDisclosure, useListState } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import {
IconBox,
IconCategory,
IconCopy,
IconCursorText,
IconDeviceFloppy,
IconDotsVertical,
IconDownload,
IconFolderFilled,
IconLock,
IconLockOff,
Expand All @@ -32,6 +34,8 @@ import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import { useTranslation } from 'next-i18next';
import Head from 'next/head';
import Link from 'next/link';
import { useState } from 'react';
import { RenameBoardModal } from '~/components/Dashboard/Modals/RenameBoard/RenameBoardModal';
import { openCreateBoardModal } from '~/components/Manage/Board/create-board.modal';
import { openDeleteBoardModal } from '~/components/Manage/Board/delete-board.modal';
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
Expand All @@ -42,16 +46,14 @@ import { getServerSideTranslations } from '~/tools/server/getServerSideTranslati
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
import { manageNamespaces } from '~/tools/server/translation-namespaces';
import { api } from '~/utils/api';
import { notifications } from '@mantine/notifications';
import { RenameBoardModal } from '~/components/Dashboard/Modals/RenameBoard/RenameBoardModal';
import { useState } from 'react';

// Infer return type from the `getServerSideProps` function
export default function BoardsPage({
boards,
session,
boards,
session,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const [openedRenameBoardModal, { open: openRenameBoardModal, close: closeRenameBoardModal }] = useDisclosure(false);
const [openedRenameBoardModal, { open: openRenameBoardModal, close: closeRenameBoardModal }] =
useDisclosure(false);
const [renameBoardName, setRenameBoardName] = useState<{ boardName: string }>();

const { data, refetch } = api.boards.all.useQuery(undefined, {
Expand Down Expand Up @@ -79,6 +81,11 @@ export default function BoardsPage({
});

const [deletingDashboards, { append, filter }] = useListState<string>([]);
const downloadAllBoards = async () => {
const a = document.createElement('a');
a.href = `/api/download`;
a.click();
};

const { t } = useTranslation('manage/boards');

Expand All @@ -90,22 +97,37 @@ export default function BoardsPage({
<title>{metaTitle}</title>
</Head>

<Modal opened={openedRenameBoardModal} onClose={closeRenameBoardModal}
title={t('cards.menu.rename.modal.title', { name: renameBoardName?.boardName })}>
<RenameBoardModal boardName={renameBoardName?.boardName as string} configNames={data.map(board => board.name)}
onClose={closeRenameBoardModal} />
<Modal
opened={openedRenameBoardModal}
onClose={closeRenameBoardModal}
title={t('cards.menu.rename.modal.title', { name: renameBoardName?.boardName })}
>
<RenameBoardModal
boardName={renameBoardName?.boardName as string}
configNames={data.map((board) => board.name)}
onClose={closeRenameBoardModal}
/>
</Modal>

<Group position="apart">
<Title mb="xl">{t('pageTitle')}</Title>
{session?.user.isAdmin && (
<Button
onClick={openCreateBoardModal}
leftIcon={<IconPlus size="1rem" />}
variant="default"
>
{t('buttons.create')}
</Button>
<Group>
<Button
variant="outline"
onClick={downloadAllBoards}
leftIcon={<IconDownload size="1rem" />}
>
Download all boards
</Button>
<Button
onClick={openCreateBoardModal}
leftIcon={<IconPlus size="1rem" />}
variant="default"
>
{t('buttons.create')}
</Button>
</Group>
)}
</Group>

Expand Down Expand Up @@ -200,18 +222,20 @@ export default function BoardsPage({
boardName: board.name,
});
}}
icon={<IconCopy size={'1rem'} />}>
icon={<IconCopy size={'1rem'} />}
>
{t('cards.menu.duplicate')}
</Menu.Item>
<Menu.Item
onClick={() => {
setRenameBoardName({
boardName: board.name as string
boardName: board.name as string,
});
openRenameBoardModal();
}}
icon={<IconCursorText size={'1rem'} />}
disabled={board.name === 'default'}>
disabled={board.name === 'default'}
>
{t('cards.menu.rename.label')}
</Menu.Item>
<Menu.Item
Expand Down Expand Up @@ -264,7 +288,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const result = checkForSessionOrAskForLogin(
context,
session,
() => session?.user.isAdmin == true,
() => session?.user.isAdmin == true
);
if (result !== undefined) {
return result;
Expand All @@ -281,7 +305,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
manageNamespaces,
context.locale,
context.req,
context.res,
context.res
);

return {
Expand Down
18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,15 @@ __metadata:
languageName: node
linkType: hard

"@types/adm-zip@npm:^0.5.5":
version: 0.5.5
resolution: "@types/adm-zip@npm:0.5.5"
dependencies:
"@types/node": "*"
checksum: 808c25b8a1c2e1c594cf9b1514e7953105cf96e19e38aa7dc109ff2537bda7345b950ef1f4e54a6e824e5503e29d24b0ff6d0aa1ff9bd4afb79ef0ef2df9ebab
languageName: node
linkType: hard

"@types/aria-query@npm:^5.0.1":
version: 5.0.4
resolution: "@types/aria-query@npm:5.0.4"
Expand Down Expand Up @@ -4031,6 +4040,13 @@ __metadata:
languageName: node
linkType: hard

"adm-zip@npm:^0.5.15":
version: 0.5.15
resolution: "adm-zip@npm:0.5.15"
checksum: 23fc108ba0ead637cf8f89431bd152017d3d2eccbbac5e77bcfa3d0209029a53921d9735c5110c06b51cf223184f4cf2fdade975f20266a64183e94717a535f4
languageName: node
linkType: hard

"aes-decrypter@npm:4.0.1, aes-decrypter@npm:^4.0.1":
version: 4.0.1
resolution: "aes-decrypter@npm:4.0.1"
Expand Down Expand Up @@ -7429,6 +7445,7 @@ __metadata:
"@trpc/next": ^10.37.1
"@trpc/react-query": ^10.37.1
"@trpc/server": ^10.37.1
"@types/adm-zip": ^0.5.5
"@types/bcryptjs": ^2.4.2
"@types/better-sqlite3": ^7.6.5
"@types/cookies": ^0.7.7
Expand All @@ -7447,6 +7464,7 @@ __metadata:
"@vitest/coverage-c8": ^0.33.0
"@vitest/coverage-v8": ^0.34.5
"@vitest/ui": ^0.34.4
adm-zip: ^0.5.15
axios: ^1.0.0
bcryptjs: ^2.4.3
better-sqlite3: ^8.6.0
Expand Down

0 comments on commit a879358

Please sign in to comment.