import React, { useEffect, useMemo, useState } from 'react'; import { Checkbox, DatePicker, useAPIClient, useCompile } from '@tachybase/client'; import { FormItem } from '@tachybase/components'; import { InboxOutlined, PlusOutlined, ReloadOutlined, UploadOutlined } from '@ant-design/icons'; import { Alert, App, Button, Card, Divider, message, Modal, Space, Spin, Table, Tabs, Upload, UploadProps } from 'antd'; import { saveAs } from 'file-saver'; import { useDuplicatorTranslation } from './locale'; const { Dragger } = Upload; function useUploadProps(props: UploadProps): any { const onChange = (param) => { props.onChange?.(param); }; const api = useAPIClient(); return { ...props, customRequest({ action, data, file, filename, headers, onError, onProgress, onSuccess, withCredentials }) { const formData = new FormData(); if (data) { Object.keys(data).forEach((key) => { formData.append(key, data[key]); }); } formData.append(filename, file); // eslint-disable-next-line promise/catch-or-return api.axios .post(action, formData, { withCredentials, headers, onUploadProgress: ({ total, loaded }) => { onProgress({ percent: Math.round((loaded / total) * 100).toFixed(2) }, file); }, }) .then(({ data }) => { onSuccess(data, file); }) .catch(onError) .finally(() => {}); return { abort() { console.log('upload progress is aborted.'); }, }; }, onChange, }; } const LearnMore: any = (props: { collectionsData?: any; isBackup?: boolean }) => { const { collectionsData } = props; const { t } = useDuplicatorTranslation(); const [isModalOpen, setIsModalOpen] = useState(false); const [dataSource, setDataSource] = useState(collectionsData); useEffect(() => { setDataSource(collectionsData); }, [collectionsData]); const apiClient = useAPIClient(); const compile = useCompile(); const resource = useMemo(() => { return apiClient.resource('backupFiles'); }, [apiClient]); const showModal = async () => { if (props.isBackup) { const data = await resource.dumpableCollections(); setDataSource(data?.data); setIsModalOpen(true); } setIsModalOpen(true); }; const handleOk = () => { setIsModalOpen(false); }; const handleCancel = () => { setIsModalOpen(false); }; const columns = [ { title: t('Collection'), dataIndex: 'collection', key: 'collection', render: (_, data) => { const title = compile(data.title); const name = data.name; return name === title ? ( title ) : (
{data.name} ({compile(data.title)})
); }, }, { title: t('Origin'), dataIndex: 'origin', key: 'origin', width: '50%', }, ]; const items = Object.keys(dataSource || {}).map((item) => { return { key: item, label: t(`${item}.title`), children: ( <> ), }; }); return ( <> {t('Learn more')} ); }; const Restore: React.FC = ({ ButtonComponent = Button, title, upload = false, fileData }) => { const { t } = useDuplicatorTranslation(); const [dataTypes, setDataTypes] = useState(['required']); const [isModalOpen, setIsModalOpen] = useState(false); const [restoreData, setRestoreData] = useState(null); const [loading, setLoading] = useState(false); const apiClient = useAPIClient(); const resource = useMemo(() => { return apiClient.resource('backupFiles'); }, [apiClient]); const [dataSource, setDataSource] = useState([]); useEffect(() => { setDataSource( Object.keys(restoreData?.dumpableCollectionsGroupByGroup || []).map((key) => ({ value: key, label: t(`${key}.title`), disabled: ['required', 'skipped'].includes(key), })), ); }, [restoreData]); const showModal = async () => { setIsModalOpen(true); if (!upload) { setLoading(true); const { data } = await resource.get({ filterByTk: fileData.name }); setDataSource( Object.keys(data?.data?.meta?.dumpableCollectionsGroupByGroup || []).map((key) => ({ value: key, label: t(`${key}.title`), disabled: ['required', 'skipped'].includes(key), })), ); setRestoreData(data?.data?.meta); setLoading(false); } }; const handleOk = () => { resource.restore({ values: { dataTypes, filterByTk: fileData?.name, key: restoreData?.key, }, }); setIsModalOpen(false); }; const handleCancel = () => { setIsModalOpen(false); setRestoreData(null); setDataTypes(['required']); }; return ( <> {title} {upload && !restoreData && } {(!upload || restoreData) && [ {t('Select the data to be restored')} ( ): ,
setDataTypes(checkValue)} />
, ]}
); }; const NewBackup: React.FC = ({ ButtonComponent = Button, refresh }) => { const { t } = useDuplicatorTranslation(); const [isModalOpen, setIsModalOpen] = useState(false); const [dataTypes, setBackupData] = useState(['required']); const apiClient = useAPIClient(); const [dataSource, setDataSource] = useState([]); const showModal = async () => { const { data } = await apiClient.resource('backupFiles').dumpableCollections(); setDataSource( Object.keys(data || []).map((key) => ({ value: key, label: t(`${key}.title`), disabled: ['required', 'skipped'].includes(key), })), ); setIsModalOpen(true); }; const handleOk = () => { apiClient.request({ url: 'backupFiles:create', method: 'post', data: { dataTypes, }, }); setIsModalOpen(false); setBackupData(['required']); setTimeout(() => { refresh(); }, 500); }; const handleCancel = () => { setIsModalOpen(false); setBackupData(['required']); }; return ( <> } type="primary" onClick={showModal}> {t('New backup')} {t('Select the data to be backed up')} ( ):
setBackupData(checkValue)} value={dataTypes} />
); }; const RestoreUpload: React.FC = (props: any) => { const { t } = useDuplicatorTranslation(); const uploadProps: UploadProps = { multiple: false, action: '/backupFiles:upload', onChange(info) { if (info.fileList.length > 1) { info.fileList.splice(0, info.fileList.length - 1); // 只保留一个文件 } const { status } = info.file; if (status === 'done') { message.success(`${info.file.name} ` + t('file uploaded successfully')); props.setRestoreData({ ...info.file.response?.data?.meta, key: info.file.response?.data.key }); } else if (status === 'error') { message.error(`${info.file.name} ` + t('file upload failed')); } }, onDrop(e) { console.log('Dropped files', e.dataTransfer.files); }, }; return (

{t('Click or drag file to this area to upload')}

); }; export const BackupAndRestoreList = () => { const { t } = useDuplicatorTranslation(); const apiClient = useAPIClient(); const [dataSource, setDataSource] = useState([]); const [loading, setLoading] = useState(false); const [downloadTarget, setDownloadTarget] = useState(false); const { modal } = App.useApp(); const resource = useMemo(() => { return apiClient.resource('backupFiles'); }, [apiClient]); useEffect(() => { queryFieldList(); }, []); const queryFieldList = async () => { setLoading(true); const { data } = await resource.list(); setDataSource(data.data); setLoading(false); }; const handleDownload = async (fileData) => { setDownloadTarget(fileData.name); const data = await apiClient.request({ url: 'backupFiles:download', method: 'get', params: { filterByTk: fileData.name, }, responseType: 'blob', }); setDownloadTarget(false); const blob = new Blob([data.data]); saveAs(blob, fileData.name); }; const handleRefresh = async () => { await queryFieldList(); }; const handleDestory = (fileData) => { modal.confirm({ title: t('Delete record', { ns: 'client' }), content: t('Are you sure you want to delete it?', { ns: 'client' }), onOk: async () => { await resource.destroy({ filterByTk: fileData.name }); await queryFieldList(); message.success(t('Deleted successfully')); }, }); }; return (
{t('Restore backup from local')} } />
{ return data.inProgress ? { colSpan: 4, } : {}; }, render: (name, data) => data.inProgress ? (
{name}({t('Backing up')}...)
) : (
{name}
), }, { title: t('File size'), dataIndex: 'fileSize', onCell: (data) => { return data.inProgress ? { colSpan: 0, } : {}; }, }, { title: t('Created at', { ns: 'client' }), dataIndex: 'createdAt', onCell: (data) => { return data.inProgress ? { colSpan: 0, } : {}; }, render: (value) => { return ; }, }, { title: t('Actions', { ns: 'client' }), dataIndex: 'actions', onCell: (data) => { return data.inProgress ? { colSpan: 0, } : {}; }, render: (_, record) => ( }> handleDownload(record)}> {t('Download')} handleDestory(record)}>{t('Delete')} ), }, ]} /> ); };