mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-04 22:40:11 +00:00
feat: add Cloudflare R2 support with detailed backup recommendations and localization updates
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
export const BACKUP_DEFAULT_TIMEZONE = 'UTC';
|
||||
export const BACKUP_DEFAULT_RETENTION_COUNT = 30;
|
||||
export const BACKUP_DEFAULT_S3_REGION = 'auto';
|
||||
export const BACKUP_DEFAULT_REMOTE_PATH = 'nodewarden';
|
||||
export const BACKUP_DEFAULT_S3_ROOT_PATH = '';
|
||||
export const BACKUP_DEFAULT_WEBDAV_REMOTE_PATH = 'nodewarden';
|
||||
export const BACKUP_DEFAULT_INTERVAL_HOURS = 24;
|
||||
export const BACKUP_DEFAULT_START_TIME = '03:00';
|
||||
|
||||
@@ -109,14 +110,14 @@ export function createDefaultBackupDestinationConfig(type: BackupDestinationType
|
||||
region: BACKUP_DEFAULT_S3_REGION,
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
rootPath: BACKUP_DEFAULT_REMOTE_PATH,
|
||||
rootPath: BACKUP_DEFAULT_S3_ROOT_PATH,
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseUrl: '',
|
||||
username: '',
|
||||
password: '',
|
||||
remotePath: BACKUP_DEFAULT_REMOTE_PATH,
|
||||
remotePath: BACKUP_DEFAULT_WEBDAV_REMOTE_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,30 @@ function renderRecommendedProviderDetails(provider: RecommendedProvider) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'cloudflare-r2':
|
||||
return (
|
||||
<div className="backup-recommendation-steps">
|
||||
<div className="backup-recommendation-step">
|
||||
<strong>1.</strong> {t('txt_backup_recommend_cloudflare_r2_step_1_prefix')}{' '}
|
||||
<a href={provider.bucketUrl} target="_blank" rel="noreferrer">{t('txt_backup_recommend_cloudflare_r2_bucket_link')}</a>
|
||||
{t('txt_backup_recommend_cloudflare_r2_step_1_suffix')}
|
||||
</div>
|
||||
<div className="backup-recommendation-step">
|
||||
<strong>2.</strong> {t('txt_backup_recommend_cloudflare_r2_step_2_prefix')}{' '}
|
||||
<a href={provider.apiTokenUrl} target="_blank" rel="noreferrer">{t('txt_backup_recommend_cloudflare_r2_api_link')}</a>
|
||||
{t('txt_backup_recommend_cloudflare_r2_step_2_suffix')}
|
||||
</div>
|
||||
<div className="backup-recommendation-step">
|
||||
<strong>3.</strong> {t('txt_backup_recommend_cloudflare_r2_step_3')}
|
||||
</div>
|
||||
<div className="backup-recommendation-step">
|
||||
<strong>4.</strong> {t('txt_backup_recommend_cloudflare_r2_step_4')}
|
||||
</div>
|
||||
<div className="backup-recommendation-step">
|
||||
<strong>5.</strong> {t('txt_backup_recommend_cloudflare_r2_step_5')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +196,7 @@ export function BackupDestinationDetail(props: BackupDestinationDetailProps) {
|
||||
{props.selectedRecommendedProvider.id === 'infinicloud' ? t('txt_backup_recommend_infinicloud_summary')
|
||||
: props.selectedRecommendedProvider.id === 'koofr' ? t('txt_backup_recommend_koofr_summary')
|
||||
: props.selectedRecommendedProvider.id === 'backblaze-b2' ? t('txt_backup_recommend_backblaze_summary')
|
||||
: props.selectedRecommendedProvider.id === 'cloudflare-r2' ? t('txt_backup_recommend_cloudflare_r2_summary')
|
||||
: t('txt_backup_recommend_pcloud_summary')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -412,7 +437,7 @@ export function BackupDestinationDetail(props: BackupDestinationDetailProps) {
|
||||
className="input"
|
||||
value={(props.selectedDestination.destination as WebDavBackupDestination).remotePath}
|
||||
disabled={props.loadingSettings || props.disableWhileBusy}
|
||||
placeholder="nodewarden/backups"
|
||||
placeholder="nodewarden"
|
||||
onInput={(event) => props.onUpdateDestination((destination) => ({
|
||||
...destination,
|
||||
destination: {
|
||||
@@ -529,7 +554,7 @@ export function BackupDestinationDetail(props: BackupDestinationDetailProps) {
|
||||
className="input"
|
||||
value={(props.selectedDestination.destination as S3BackupDestination).rootPath}
|
||||
disabled={props.loadingSettings || props.disableWhileBusy}
|
||||
placeholder="nodewarden/backups"
|
||||
placeholder=""
|
||||
onInput={(event) => props.onUpdateDestination((destination) => ({
|
||||
...destination,
|
||||
destination: {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Download, FileUp } from 'lucide-preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import type { RecommendedProvider } from '@/lib/backup-recommendations';
|
||||
import { hasLinkedStorages } from '@/lib/backup-recommendations';
|
||||
import { t } from '@/lib/i18n';
|
||||
import { BackupIncludeAttachmentsField } from './BackupIncludeAttachmentsField';
|
||||
|
||||
const MOBILE_RECOMMENDATIONS_QUERY = '(max-width: 760px)';
|
||||
|
||||
interface BackupOperationsSidebarProps {
|
||||
disableWhileBusy: boolean;
|
||||
exporting: boolean;
|
||||
@@ -18,7 +21,30 @@ interface BackupOperationsSidebarProps {
|
||||
onSelectProvider: (providerId: string) => void;
|
||||
}
|
||||
|
||||
function getDefaultRecommendationsOpen() {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
||||
return true;
|
||||
}
|
||||
return !window.matchMedia(MOBILE_RECOMMENDATIONS_QUERY).matches;
|
||||
}
|
||||
|
||||
export function BackupOperationsSidebar(props: BackupOperationsSidebarProps) {
|
||||
const [recommendationsOpen, setRecommendationsOpen] = useState(getDefaultRecommendationsOpen);
|
||||
const [recommendationsTouched, setRecommendationsTouched] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function' || recommendationsTouched) {
|
||||
return;
|
||||
}
|
||||
|
||||
const media = window.matchMedia(MOBILE_RECOMMENDATIONS_QUERY);
|
||||
const syncOpenState = () => setRecommendationsOpen(!media.matches);
|
||||
|
||||
syncOpenState();
|
||||
media.addEventListener('change', syncOpenState);
|
||||
return () => media.removeEventListener('change', syncOpenState);
|
||||
}, [recommendationsTouched]);
|
||||
|
||||
return (
|
||||
<aside className="backup-operations-sidebar">
|
||||
<div className="section-head">
|
||||
@@ -41,7 +67,14 @@ export function BackupOperationsSidebar(props: BackupOperationsSidebarProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<details className="backup-recommendations-disclosure">
|
||||
<details
|
||||
className="backup-recommendations-disclosure"
|
||||
open={recommendationsOpen}
|
||||
onToggle={(event) => {
|
||||
setRecommendationsTouched(true);
|
||||
setRecommendationsOpen((event.currentTarget as HTMLDetailsElement).open);
|
||||
}}
|
||||
>
|
||||
<summary className="backup-recommendations-summary">
|
||||
<span>
|
||||
<strong>{t('txt_backup_recommend_title')}</strong>
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface RecommendedStorageLink {
|
||||
}
|
||||
|
||||
export interface RecommendedProviderBase {
|
||||
id: 'infinicloud' | 'koofr' | 'pcloud' | 'backblaze-b2';
|
||||
id: 'infinicloud' | 'koofr' | 'pcloud' | 'backblaze-b2' | 'cloudflare-r2';
|
||||
name: string;
|
||||
capacity: string;
|
||||
protocol: 'webdav' | 's3';
|
||||
@@ -34,7 +34,13 @@ export interface BackblazeB2Provider extends RecommendedProviderBase {
|
||||
applicationKeysUrl: string;
|
||||
}
|
||||
|
||||
export type RecommendedProvider = InfinicloudProvider | KoofrProvider | PcloudProvider | BackblazeB2Provider;
|
||||
export interface CloudflareR2Provider extends RecommendedProviderBase {
|
||||
id: 'cloudflare-r2';
|
||||
bucketUrl: string;
|
||||
apiTokenUrl: string;
|
||||
}
|
||||
|
||||
export type RecommendedProvider = InfinicloudProvider | KoofrProvider | PcloudProvider | BackblazeB2Provider | CloudflareR2Provider;
|
||||
|
||||
export const RECOMMENDED_PROVIDERS: RecommendedProvider[] = [
|
||||
{
|
||||
@@ -76,6 +82,15 @@ export const RECOMMENDED_PROVIDERS: RecommendedProvider[] = [
|
||||
bucketsUrl: 'https://secure.backblaze.com/b2_buckets.htm',
|
||||
applicationKeysUrl: 'https://secure.backblaze.com/app_keys.htm',
|
||||
},
|
||||
{
|
||||
id: 'cloudflare-r2',
|
||||
name: 'Cloudflare R2',
|
||||
capacity: '10G',
|
||||
protocol: 's3',
|
||||
signupUrl: 'https://dash.cloudflare.com/?to=/:account/r2/new',
|
||||
bucketUrl: 'https://dash.cloudflare.com/?to=/:account/r2/new',
|
||||
apiTokenUrl: 'https://dash.cloudflare.com/?to=/:account/r2/api-tokens/create?type=user',
|
||||
},
|
||||
];
|
||||
|
||||
export function hasLinkedStorages(provider: RecommendedProvider): provider is KoofrProvider {
|
||||
|
||||
@@ -93,6 +93,16 @@ const en: Record<string, string> = {
|
||||
"txt_backup_recommend_backblaze_step_4_prefix": "Open",
|
||||
"txt_backup_recommend_backblaze_step_4_suffix": ", click Add a New Application Key, enter any Name of Key, leave the other settings unchanged, and create it.",
|
||||
"txt_backup_recommend_backblaze_step_5": "Use keyID as the access key and applicationKey as the secret key.",
|
||||
"txt_backup_recommend_cloudflare_r2_summary": "S3-compatible object storage with a 10 GB-month monthly free tier. Keep the region set to auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_bucket_link": "Create bucket page",
|
||||
"txt_backup_recommend_cloudflare_r2_api_link": "API token page",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_prefix": "Open the",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_suffix": ", enter only the bucket name, and create it directly.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_prefix": "Open the",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_suffix": ", select Object Read & Write for permissions, and create it directly.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_3": "Ignore the token value after creation. Fill Access Key ID into Access ID, and Secret Access Key into Access Password.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_4": "Set S3 Endpoint URL to https://<account ID>.r2.cloudflarestorage.com, fill the bucket name as shown, and keep the region as auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_5": "Set Path Prefix as needed, for example nodewarden/backups, or leave it empty if you do not want a folder prefix.",
|
||||
"txt_backup_add_destination": "Add Destination",
|
||||
"txt_backup_schedule_panel_title": "Automatic Schedule",
|
||||
"txt_backup_schedule_panel_note": "Each destination can keep its own daily backup schedule.",
|
||||
|
||||
@@ -93,6 +93,16 @@ const es: Record<string, string> = {
|
||||
"txt_backup_recommend_backblaze_step_4_prefix": "Abra",
|
||||
"txt_backup_recommend_backblaze_step_4_suffix": ", haga clic en Add a New Application Key, introduzca cualquier Name of Key, deje lo demás sin cambios y créelo.",
|
||||
"txt_backup_recommend_backblaze_step_5": "Use keyID como clave de acceso y applicationKey como clave secreta.",
|
||||
"txt_backup_recommend_cloudflare_r2_summary": "Almacenamiento de objetos compatible con S3 con 10 GB-month gratis al mes. Mantenga la región en auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_bucket_link": "página para crear bucket",
|
||||
"txt_backup_recommend_cloudflare_r2_api_link": "página de token API",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_prefix": "Abra la",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_suffix": ", introduzca solo el nombre del bucket y créelo directamente.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_prefix": "Abra la",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_suffix": ", seleccione Object Read & Write en permisos y créelo directamente.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_3": "Ignore el valor del token después de crearlo. Use Access Key ID como ID de acceso y Secret Access Key como contraseña de acceso.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_4": "Use https://<ID de cuenta>.r2.cloudflarestorage.com como URL del endpoint S3, rellene el nombre del bucket tal como aparece y mantenga la región en auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_5": "Configure el prefijo de ruta si lo necesita, por ejemplo nodewarden/backups, o déjelo vacío si no quiere un prefijo de carpeta.",
|
||||
"txt_backup_add_destination": "Añadir destino",
|
||||
"txt_backup_schedule_panel_title": "Programación automática",
|
||||
"txt_backup_schedule_panel_note": "Cada destino puede mantener su propia programación de copia de seguridad diaria.",
|
||||
|
||||
@@ -94,6 +94,16 @@ const ru: Record<string, string> = {
|
||||
"txt_backup_recommend_backblaze_step_4_prefix": "Откройте",
|
||||
"txt_backup_recommend_backblaze_step_4_suffix": ", нажмите Add a New Application Key, введите любое Name of Key, оставьте остальные настройки без изменений и создайте ключ.",
|
||||
"txt_backup_recommend_backblaze_step_5": "Используйте keyID как ключ доступа, а applicationKey как секретный ключ.",
|
||||
"txt_backup_recommend_cloudflare_r2_summary": "S3-совместимое объектное хранилище с бесплатным уровнем 10 GB-month в месяц. Оставьте регион auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_bucket_link": "страницу создания bucket",
|
||||
"txt_backup_recommend_cloudflare_r2_api_link": "страницу API token",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_prefix": "Откройте",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_suffix": ", введите только имя bucket и сразу создайте его.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_prefix": "Откройте",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_suffix": ", выберите Object Read & Write в разрешениях и сразу создайте токен.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_3": "После создания игнорируйте token value. Введите Access Key ID как ID доступа, а Secret Access Key как пароль доступа.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_4": "Укажите https://<ID аккаунта>.r2.cloudflarestorage.com как URL S3 endpoint, заполните имя bucket как есть и оставьте регион auto.",
|
||||
"txt_backup_recommend_cloudflare_r2_step_5": "Укажите префикс пути при необходимости, например nodewarden/backups, или оставьте пустым, если префикс папки не нужен.",
|
||||
"txt_backup_add_destination": "Добавить пункт назначения",
|
||||
"txt_backup_schedule_panel_title": "Автоматическое расписание",
|
||||
"txt_backup_schedule_panel_note": "Каждый пункт назначения может иметь собственный ежедневный график резервного копирования.",
|
||||
|
||||
@@ -89,10 +89,20 @@ const zhCN: Record<string, string> = {
|
||||
"txt_backup_recommend_backblaze_step_1": "先注册或登录 Backblaze 账号。",
|
||||
"txt_backup_recommend_backblaze_step_2_prefix": "打开",
|
||||
"txt_backup_recommend_backblaze_step_2_suffix": ",点击创建一个桶,只输入桶名字,其他地方不修改,然后创建。",
|
||||
"txt_backup_recommend_backblaze_step_3": "创建后显示的 Endpoint 填到 S3端点URL;桶名名称 填到 储桶名称;区域填写 Endpoint 中间的区域值,例如 us-west-004。",
|
||||
"txt_backup_recommend_backblaze_step_3": "创建后显示的 Endpoint 填到 S3 端点 URL;桶名字填到存储桶名称;区域填 Endpoint 中间那段,例如 us-west-004。",
|
||||
"txt_backup_recommend_backblaze_step_4_prefix": "打开",
|
||||
"txt_backup_recommend_backblaze_step_4_suffix": ",点击 Add a New Application Key,随便输入 Name of Key,其他地方不动,然后创建。",
|
||||
"txt_backup_recommend_backblaze_step_5": "生成结果里的 keyID 填到 访问 ID,applicationKey 填到 访问密码。",
|
||||
"txt_backup_recommend_cloudflare_r2_summary": "兼容 S3 的对象存储,免费层每月 10 GB-month,区域保持 auto 不改。",
|
||||
"txt_backup_recommend_cloudflare_r2_bucket_link": "创建储存桶页面",
|
||||
"txt_backup_recommend_cloudflare_r2_api_link": "API 创建页面",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_prefix": "打开",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_suffix": ",只输入存储桶名称,直接创建。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_prefix": "打开",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_suffix": ",权限全选“对象读和写”,直接创建。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_3": "创建后令牌值不用管;Access Key ID 填到 访问 ID,Secret Access Key 填到 访问密码。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_4": "把下面显示的地址填到 S3 端点 URL;存储桶名称如实填写;区域保持 auto 不改。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_5": "路径前缀按需要填写,例如 nodewarden;不想分目录可以留空。",
|
||||
"txt_backup_add_destination": "新增地点",
|
||||
"txt_backup_schedule_panel_title": "自动备份计划",
|
||||
"txt_backup_schedule_panel_note": "每个备份地点都可以单独配置自己的每日自动备份计划。",
|
||||
|
||||
@@ -93,6 +93,16 @@ const zhTW: Record<string, string> = {
|
||||
"txt_backup_recommend_backblaze_step_4_prefix": "打開",
|
||||
"txt_backup_recommend_backblaze_step_4_suffix": ",點擊 Add a New Application Key,隨便輸入 Name of Key,其他地方不動,然後創建。",
|
||||
"txt_backup_recommend_backblaze_step_5": "生成結果裡的 keyID 填存取金鑰,applicationKey 填秘密金鑰。",
|
||||
"txt_backup_recommend_cloudflare_r2_summary": "兼容 S3 的對象儲存,免費層每月 10 GB-month,區域保持 auto 不改。",
|
||||
"txt_backup_recommend_cloudflare_r2_bucket_link": "創建儲存桶頁面",
|
||||
"txt_backup_recommend_cloudflare_r2_api_link": "API 創建頁面",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_prefix": "打開",
|
||||
"txt_backup_recommend_cloudflare_r2_step_1_suffix": ",只輸入儲存桶名稱,直接創建。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_prefix": "打開",
|
||||
"txt_backup_recommend_cloudflare_r2_step_2_suffix": ",權限全選「對象讀和寫」,直接創建。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_3": "創建後令牌值不用管;Access Key ID 填到存取 ID,Secret Access Key 填到存取密碼。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_4": "S3 端點 URL 填 https://<賬戶 ID>.r2.cloudflarestorage.com;儲存桶名稱如實填寫;區域保持 auto 不改。",
|
||||
"txt_backup_recommend_cloudflare_r2_step_5": "路徑前綴按需要填寫,例如 nodewarden/backups;不想分目錄可以留空。",
|
||||
"txt_backup_add_destination": "新增地點",
|
||||
"txt_backup_schedule_panel_title": "自動備份計劃",
|
||||
"txt_backup_schedule_panel_note": "每個備份地點都可以單獨配置自己的每日自動備份計劃。",
|
||||
|
||||
Reference in New Issue
Block a user