diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json index 93df0b99cd5..e430269382c 100644 --- a/apps/app/public/static/locales/en_US/translation.json +++ b/apps/app/public/static/locales/en_US/translation.json @@ -642,6 +642,7 @@ "bulk_export_started": "Please wait a moment...", "bulk_export_download_expired": "Download period has expired", "bulk_export_job_expired": "Export process was canceled because it took too long", + "bulk_export_only_available_for": "Only available for AWS or GCP", "export_in_progress": "Export in progress", "export_in_progress_explanation": "Export with the same format is already in progress. Would you like to restart to export the latest page contents?", "export_cancel_warning": "The export in progress will be canceled", diff --git a/apps/app/public/static/locales/fr_FR/translation.json b/apps/app/public/static/locales/fr_FR/translation.json index 0acd03f5d36..0bad0f3fdd9 100644 --- a/apps/app/public/static/locales/fr_FR/translation.json +++ b/apps/app/public/static/locales/fr_FR/translation.json @@ -636,6 +636,7 @@ "bulk_export_started": "Patientez s'il-vous-plait...", "bulk_export_download_expired": "La période de téléchargement a expiré", "bulk_export_job_expired": "Le traitement a été interrompu car le temps d'exportation était trop long", + "bulk_export_only_available_for": "Uniquement disponible pour AWS ou GCP", "export_in_progress": "Exportation en cours", "export_in_progress_explanation": "L'exportation avec le même format est déjà en cours. Souhaitez-vous redémarrer pour exporter le dernier contenu de la page ?", "export_cancel_warning": "L'export en cours sera annulé", diff --git a/apps/app/public/static/locales/ja_JP/translation.json b/apps/app/public/static/locales/ja_JP/translation.json index 17bda23d008..46daaaf1dcf 100644 --- a/apps/app/public/static/locales/ja_JP/translation.json +++ b/apps/app/public/static/locales/ja_JP/translation.json @@ -675,6 +675,7 @@ "bulk_export_started": "ただいま準備中です...", "bulk_export_download_expired": "ダウンロード期限が切れました", "bulk_export_job_expired": "エクスポート時間が長すぎるため、処理が中断されました", + "bulk_export_only_available_for": "AWS と GCP のみ対応しています", "export_in_progress": "エクスポート進行中", "export_in_progress_explanation": "既に同じ形式でのエクスポートが進行中です。最新のページ内容でエクスポートを最初からやり直しますか?", "export_cancel_warning": "進行中のエクスポートはキャンセルされます", diff --git a/apps/app/public/static/locales/zh_CN/translation.json b/apps/app/public/static/locales/zh_CN/translation.json index 904097ec755..ca6a7da42f5 100644 --- a/apps/app/public/static/locales/zh_CN/translation.json +++ b/apps/app/public/static/locales/zh_CN/translation.json @@ -645,6 +645,7 @@ "bulk_export_started": "目前我们正在准备...", "bulk_export_download_expired": "下载期限已过", "bulk_export_job_expired": "由于导出时间太长,处理被中断", + "bulk_export_only_available_for": "仅适用于 AWS 或 GCP", "export_in_progress": "导出正在进行中", "export_in_progress_explanation": "已在进行相同格式的导出。您要重新启动以导出最新的页面内容吗?", "export_cancel_warning": "正在进行的导出将被取消", diff --git a/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx b/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx index 6928aece3b5..35cd8b2f983 100644 --- a/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx +++ b/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next'; import AdminAppContainer from '~/client/services/AdminAppContainer'; import { toastSuccess, toastError } from '~/client/util/toastr'; +import { FileUploadType } from '~/interfaces/file-uploader'; import { withUnstatedContainers } from '../../UnstatedUtils'; import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; @@ -16,9 +17,6 @@ import type { AzureSettingMoleculeProps } from './AzureSetting'; import { GcsSettingMolecule } from './GcsSetting'; import type { GcsSettingMoleculeProps } from './GcsSetting'; - -const fileUploadTypes = ['aws', 'gcs', 'azure', 'gridfs', 'local'] as const; - type FileUploadSettingMoleculeProps = { fileUploadType: string isFixedFileUploadByEnvVar: boolean @@ -45,7 +43,7 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
- {fileUploadTypes.map((type) => { + {Object.values(FileUploadType).map((type) => { return (
{ // eslint-disable-next-line no-alert const answer = window.confirm(t('sync-latest-revision-body.confirm')); @@ -141,13 +144,25 @@ const PageOperationMenuItems = (props: PageOperationMenuItemsProps): JSX.Element {/* Bulk export */} - + + cloud_download + {t('page_export.bulk_export')} + + + setIsBulkExportTooltipOpen(!isBulkExportTooltipOpen)} > - cloud_download - {t('page_export.bulk_export')} - + {t('page_export.bulk_export_only_available_for')} + diff --git a/apps/app/src/features/page-bulk-export/interfaces/page-bulk-export.ts b/apps/app/src/features/page-bulk-export/interfaces/page-bulk-export.ts index dd78bc855fc..29561865fe4 100644 --- a/apps/app/src/features/page-bulk-export/interfaces/page-bulk-export.ts +++ b/apps/app/src/features/page-bulk-export/interfaces/page-bulk-export.ts @@ -3,6 +3,8 @@ import type { IAttachment, IPage, IRevision, IUser, Ref, } from '@growi/core'; +import { FileUploadTypeForEnvVar } from '~/interfaces/file-uploader'; + export const PageBulkExportFormat = { md: 'md', pdf: 'pdf', @@ -45,3 +47,5 @@ export interface IPageBulkExportPageSnapshot { path: string, // page path when export was stared revision: Ref, // page revision when export was stared } + +export const PageBulkExportEnabledFileUploadTypes = [FileUploadTypeForEnvVar.aws, FileUploadTypeForEnvVar.gcs, FileUploadTypeForEnvVar.gcp] as const; diff --git a/apps/app/src/features/page-bulk-export/server/service/page-bulk-export-job-cron.ts b/apps/app/src/features/page-bulk-export/server/service/page-bulk-export-job-cron.ts index 4923b0a66eb..8f6f12f72e0 100644 --- a/apps/app/src/features/page-bulk-export/server/service/page-bulk-export-job-cron.ts +++ b/apps/app/src/features/page-bulk-export/server/service/page-bulk-export-job-cron.ts @@ -4,7 +4,7 @@ import { configManager } from '~/server/service/config-manager'; import CronService from '~/server/service/cron'; import loggerFactory from '~/utils/logger'; -import { PageBulkExportJobInProgressStatus, PageBulkExportJobStatus } from '../../interfaces/page-bulk-export'; +import { PageBulkExportEnabledFileUploadTypes, PageBulkExportJobInProgressStatus, PageBulkExportJobStatus } from '../../interfaces/page-bulk-export'; import type { PageBulkExportJobDocument } from '../models/page-bulk-export-job'; import PageBulkExportJob from '../models/page-bulk-export-job'; @@ -29,6 +29,9 @@ class PageBulkExportJobCronService extends CronService { } override async executeJob(): Promise { + const isPageBulkExportEnabled = PageBulkExportEnabledFileUploadTypes.includes(configManager.getConfig('crowi', 'app:fileUploadType')); + if (!isPageBulkExportEnabled) return; + await this.deleteExpiredExportJobs(); await this.deleteDownloadExpiredExportJobs(); await this.deleteFailedExportJobs(); diff --git a/apps/app/src/interfaces/file-uploader.ts b/apps/app/src/interfaces/file-uploader.ts new file mode 100644 index 00000000000..eb3eb331097 --- /dev/null +++ b/apps/app/src/interfaces/file-uploader.ts @@ -0,0 +1,28 @@ +// file upload types actually supported by the app +export const FileUploadType = { + aws: 'aws', + gcs: 'gcs', + azure: 'azure', + gridfs: 'gridfs', + local: 'local', +} as const; + +export type FileUploadType = typeof FileUploadType[keyof typeof FileUploadType] + +// file upload type strings you can specify in the env variable +export const FileUploadTypeForEnvVar = { + ...FileUploadType, + mongo: 'mongo', + mongodb: 'mongodb', + gcp: 'gcp', +} as const; + +export type FileUploadTypeForEnvVar = typeof FileUploadTypeForEnvVar[keyof typeof FileUploadTypeForEnvVar] + +// mapping from env variable to actual module name +export const EnvToModuleMappings = { + ...FileUploadTypeForEnvVar, + mongo: 'gridfs', + mongodb: 'gridfs', + gcp: 'gcs', +} as const; diff --git a/apps/app/src/pages/[[...path]].page.tsx b/apps/app/src/pages/[[...path]].page.tsx index 496bc139639..fad6ab1915f 100644 --- a/apps/app/src/pages/[[...path]].page.tsx +++ b/apps/app/src/pages/[[...path]].page.tsx @@ -23,6 +23,7 @@ import superjson from 'superjson'; import { BasicLayout } from '~/components/Layout/BasicLayout'; import { PageView } from '~/components/PageView/PageView'; import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript'; +import { PageBulkExportEnabledFileUploadTypes } from '~/features/page-bulk-export/interfaces/page-bulk-export'; import { SupportedAction, type SupportedActionType } from '~/interfaces/activity'; import type { CrowiRequest } from '~/interfaces/crowi-request'; import { RegistrationMode } from '~/interfaces/registration-mode'; @@ -42,7 +43,7 @@ import { useCsrfToken, useIsSearchScopeChildrenAsDefault, useIsEnabledMarp, useCurrentPathname, useIsSlackConfigured, useRendererConfig, useGrowiCloudUri, useIsAllReplyShown, useIsContainerFluid, useIsNotCreatable, - useIsUploadAllFileAllowed, useIsUploadEnabled, + useIsUploadAllFileAllowed, useIsUploadEnabled, useIsPageBulkExportEnabled, useElasticsearchMaxBodyLengthToIndex, useIsLocalAccountRegistrationEnabled, } from '~/stores-universal/context'; @@ -177,6 +178,7 @@ type Props = CommonProps & { isContainerFluid: boolean, isUploadEnabled: boolean, isUploadAllFileAllowed: boolean, + isPageBulkExportEnabled: boolean, isEnabledStaleNotification: boolean, isEnabledAttachTitleHeader: boolean, // isEnabledLinebreaks: boolean, @@ -239,6 +241,7 @@ const Page: NextPageWithLayout = (props: Props) => { useIsUploadAllFileAllowed(props.isUploadAllFileAllowed); useIsUploadEnabled(props.isUploadEnabled); + useIsPageBulkExportEnabled(props.isPageBulkExportEnabled); useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled); @@ -560,6 +563,7 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing'); props.isUploadAllFileAllowed = crowi.fileUploadService.getFileUploadEnabled(); props.isUploadEnabled = crowi.fileUploadService.getIsUploadable(); + props.isPageBulkExportEnabled = PageBulkExportEnabledFileUploadTypes.includes(configManager.getConfig('crowi', 'app:fileUploadType')); props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup && configManager.getConfig('crowi', 'security:registrationMode') !== RegistrationMode.CLOSED; diff --git a/apps/app/src/server/routes/apiv3/app-settings.js b/apps/app/src/server/routes/apiv3/app-settings.js index b0e6b3b86c1..aa93e87d613 100644 --- a/apps/app/src/server/routes/apiv3/app-settings.js +++ b/apps/app/src/server/routes/apiv3/app-settings.js @@ -301,7 +301,6 @@ module.exports = (crowi) => { }); - /** * @swagger * diff --git a/apps/app/src/server/service/file-uploader/index.ts b/apps/app/src/server/service/file-uploader/index.ts index 29f377827ca..8dcd8772340 100644 --- a/apps/app/src/server/service/file-uploader/index.ts +++ b/apps/app/src/server/service/file-uploader/index.ts @@ -1,3 +1,4 @@ +import { EnvToModuleMappings } from '~/interfaces/file-uploader'; import type Crowi from '~/server/crowi'; import loggerFactory from '~/utils/logger'; @@ -9,19 +10,8 @@ export type { FileUploader } from './file-uploader'; const logger = loggerFactory('growi:service:FileUploaderServise'); -const envToModuleMappings = { - aws: 'aws', - local: 'local', - mongo: 'gridfs', - mongodb: 'gridfs', - gridfs: 'gridfs', - gcp: 'gcs', - gcs: 'gcs', - azure: 'azure', -}; - export const getUploader = (crowi: Crowi): FileUploader => { - const method = envToModuleMappings[configManager.getConfig('crowi', 'app:fileUploadType')]; + const method = EnvToModuleMappings[configManager.getConfig('crowi', 'app:fileUploadType')]; const modulePath = `./${method}`; const uploader = require(modulePath)(crowi); diff --git a/apps/app/src/stores-universal/context.tsx b/apps/app/src/stores-universal/context.tsx index c020cb75720..fdffe84646d 100644 --- a/apps/app/src/stores-universal/context.tsx +++ b/apps/app/src/stores-universal/context.tsx @@ -168,6 +168,10 @@ export const useIsUploadAllFileAllowed = (initialData?: boolean): SWRResponse => { + return useContextSWR('isPageBulkExportEnabled', initialData); +}; + export const useShowPageLimitationL = (initialData?: number): SWRResponse => { return useContextSWR('showPageLimitationL', initialData); };