feat: add download functionality for board configuration files (#2111)

This commit is contained in:
Meier Lukas
2024-08-24 14:39:13 +02:00
committed by GitHub
parent cc240f4f87
commit a87935875d
4 changed files with 100 additions and 22 deletions

34
src/pages/api/download.ts Normal file
View File

@@ -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;

View File

@@ -13,6 +13,7 @@ import {
Title,
} from '@mantine/core';
import { useDisclosure, useListState } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import {
IconBox,
IconCategory,
@@ -20,6 +21,7 @@ import {
IconCursorText,
IconDeviceFloppy,
IconDotsVertical,
IconDownload,
IconFolderFilled,
IconLock,
IconLockOff,
@@ -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';
@@ -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, {
@@ -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');
@@ -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>
@@ -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
@@ -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;
@@ -281,7 +305,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
manageNamespaces,
context.locale,
context.req,
context.res,
context.res
);
return {