diff --git a/app/(protected)/admin/system/s3-list.tsx b/app/(protected)/admin/system/s3-list.tsx index c4b982f..5da6a49 100644 --- a/app/(protected)/admin/system/s3-list.tsx +++ b/app/(protected)/admin/system/s3-list.tsx @@ -7,8 +7,8 @@ import { useTranslations } from "next-intl"; import { toast } from "sonner"; import useSWR from "swr"; -import { BucketItem, CloudStorageCredentials } from "@/lib/r2"; -import { cn, fetcher } from "@/lib/utils"; +import { BucketItem, CloudStorageCredentials } from "@/lib/s3"; +import { cn, fetcher, formatFileSize } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -365,7 +365,7 @@ export default function S3Configs({}: {}) { {/* buckets */} {config.buckets.map((bucket, index2) => ( +
+
+ + + + + + + + {t("maxStorageTooltip")} + + + +
+
+ + updateBucket(index2, { + file_size: e.target.value, + }) + } + /> + {bucket.file_size && ( + + ≈{formatFileSize(Number(bucket.file_size))} + + )} +
+
+
+ + + updateBucket(index2, { + max_files: e.target.value, + }) + } + /> +
+
+
+ + + + + + + + {t("maxStorageTooltip")} + + + +
+
+ + updateBucket(index2, { + max_storage: e.target.value, + }) + } + /> + {bucket.max_storage && ( + + ≈{formatFileSize(Number(bucket.max_storage))} + + )} +
+
+
@@ -567,7 +644,7 @@ export default function S3Configs({}: {}) {
{t("How to get the S3 credentials?")} @@ -592,6 +669,7 @@ export default function S3Configs({}: {}) { region: "auto", custom_domain: "", file_size: "26214400", + max_storage: "", public: true, }, ], diff --git a/app/api/storage/admin/s3/files/route.ts b/app/api/storage/admin/s3/files/route.ts index 003bafb..f59ea22 100644 --- a/app/api/storage/admin/s3/files/route.ts +++ b/app/api/storage/admin/s3/files/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getUserFiles, softDeleteUserFiles } from "@/lib/dto/files"; import { getMultipleConfigs } from "@/lib/dto/system-config"; import { checkUserStatus } from "@/lib/dto/user"; -import { createS3Client, deleteFile, getSignedUrlForDownload } from "@/lib/r2"; +import { createS3Client, deleteFile, getSignedUrlForDownload } from "@/lib/s3"; import { getCurrentUser } from "@/lib/session"; export async function GET(req: NextRequest) { diff --git a/app/api/storage/s3/files/route.ts b/app/api/storage/s3/files/route.ts index 0c4022d..6387771 100644 --- a/app/api/storage/s3/files/route.ts +++ b/app/api/storage/s3/files/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getUserFiles, softDeleteUserFiles } from "@/lib/dto/files"; import { getMultipleConfigs } from "@/lib/dto/system-config"; import { checkUserStatus } from "@/lib/dto/user"; -import { createS3Client, deleteFile, getSignedUrlForDownload } from "@/lib/r2"; +import { createS3Client, deleteFile, getSignedUrlForDownload } from "@/lib/s3"; import { getCurrentUser } from "@/lib/session"; export async function GET(req: NextRequest) { diff --git a/app/api/storage/s3/upload/route.ts b/app/api/storage/s3/upload/route.ts index 98e8ec1..0977017 100644 --- a/app/api/storage/s3/upload/route.ts +++ b/app/api/storage/s3/upload/route.ts @@ -2,12 +2,11 @@ import { NextRequest, NextResponse } from "next/server"; import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { getPlanQuota } from "@/lib/dto/plan"; +import { getBucketStorageUsage } from "@/lib/dto/files"; import { getMultipleConfigs } from "@/lib/dto/system-config"; import { checkUserStatus } from "@/lib/dto/user"; -import { createS3Client } from "@/lib/r2"; +import { createS3Client } from "@/lib/s3"; import { getCurrentUser } from "@/lib/session"; -import { restrictByTimeRange } from "@/lib/team"; import { generateFileKey } from "@/lib/utils"; export async function POST(request: NextRequest) { @@ -44,21 +43,56 @@ export async function POST(request: NextRequest) { }); } - const plan = await getPlanQuota(user.team!); - for (const file of files) { - if (Number(file.size) > Number(plan.stMaxFileSize)) { - return Response.json(`File (${file.name}) size limit exceeded`, { - status: 400, - }); + const bucketConfig = buckets.find((b) => b.bucket === bucket); + if (bucketConfig?.file_size) { + for (const file of files) { + if (Number(file.size) > Number(bucketConfig?.file_size)) { + return Response.json(`File size limit exceeded`, { + status: 400, + }); + } + } + } + // else { + // const plan = await getPlanQuota(user.team!); + // for (const file of files) { + // if (Number(file.size) > Number(plan.stMaxFileSize)) { + // return Response.json(`File (${file.name}) size limit exceeded`, { + // status: 400, + // }); + // } + // } + // } + + // 检查存储桶容量限制 + const totalUploadSize = files.reduce( + (sum, file) => sum + Number(file.size), + 0, + ); + + if (bucketConfig?.max_storage) { + const bucketUsage = await getBucketStorageUsage( + bucket, + provider, + user.id, + ); + if (bucketUsage.success && bucketUsage.data) { + const currentUsage = bucketUsage.data.totalSize; + const maxStorage = Number(bucketConfig.max_storage); + + if (currentUsage + totalUploadSize > maxStorage) { + const remainingSpace = maxStorage - currentUsage; + const remainingSpaceGB = ( + remainingSpace / + (1024 * 1024 * 1024) + ).toFixed(2); + return Response.json( + `Bucket storage limit exceeded. Remaining space: ${remainingSpaceGB} GB.`, + { status: 403 }, + ); + } } } - // const limit = await restrictByTimeRange({ - // model: "userFile", - // userId: user.id, - // limit: Number(plan.stMaxFileCount), - // rangeType: "month", - // }); - // if (limit) return Response.json(limit.statusText, { status: limit.status }); const R2 = createS3Client( providerChannel.endpoint, diff --git a/app/layout.tsx b/app/layout.tsx index 5e3e093..d2a2fcc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -43,7 +43,7 @@ export default async function RootLayout({ children }: RootLayoutProps) { disableTransitionOnChange > {children} - + diff --git a/components/file/index.tsx b/components/file/index.tsx index 01d1457..71ff404 100644 --- a/components/file/index.tsx +++ b/components/file/index.tsx @@ -7,7 +7,7 @@ import { toast } from "sonner"; import useSWR, { useSWRConfig } from "swr"; import { UserFileData } from "@/lib/dto/files"; -import { BucketItem, ClientStorageCredentials } from "@/lib/r2"; +import { BucketItem, ClientStorageCredentials } from "@/lib/s3"; import { cn, fetcher } from "@/lib/utils"; import { useMediaQuery } from "@/hooks/use-media-query"; import { @@ -56,12 +56,28 @@ export type DisplayType = "List" | "Grid"; export interface FileListData { total: number; totalSize: number; + totalFiles: number; list: UserFileData[]; } +export interface BucketUsage { + bucket: string; + provider: string; + usage: { + totalSize: number; + totalFiles: number; + }; + limits: { + maxStorage: number; + maxFiles: number; + maxSingleFileSize: number; + }; +} + export interface StorageUserPlan { stMaxTotalSize: string; stMaxFileSize: string; + stMaxFileCount: number; } export default function UserFileManager({ user, action }: FileListProps) { @@ -94,6 +110,8 @@ export default function UserFileManager({ user, action }: FileListProps) { status: "1", }); + const [bucketUsage, setBucketUsage] = useState(null); + // const isAdmin = action.includes("/admin"); const { mutate } = useSWRConfig(); @@ -135,10 +153,42 @@ export default function UserFileManager({ user, action }: FileListProps) { channel: s3Configs[0].channel, provider_name: s3Configs[0].provider_name, public: s3Configs[0].buckets[0].public, + file_size: s3Configs[0].buckets[0].file_size, + max_files: s3Configs[0].buckets[0].max_files, + max_storage: s3Configs[0].buckets[0].max_storage, }); } }, [s3Configs]); + useEffect(() => { + if ( + files && + currentBucketInfo.bucket && + currentBucketInfo.provider_name && + plan + ) { + setBucketUsage({ + bucket: currentBucketInfo.bucket, + provider: currentBucketInfo.provider_name, + usage: { + totalSize: files.totalSize, + totalFiles: files.totalFiles, + }, + limits: { + maxStorage: currentBucketInfo.max_storage + ? Number(currentBucketInfo.max_storage) + : Number(plan.stMaxTotalSize), + maxFiles: currentBucketInfo.max_files + ? Number(currentBucketInfo.max_files) + : Number(plan.stMaxFileCount), + maxSingleFileSize: currentBucketInfo.file_size + ? Number(currentBucketInfo.file_size) + : Number(plan.stMaxFileSize), + }, + }); + } + }, [files, currentBucketInfo, plan]); + const handleRefresh = () => { setSelectedFiles([]); mutate( @@ -151,18 +201,21 @@ export default function UserFileManager({ user, action }: FileListProps) { provider: ClientStorageCredentials, bucket: string, ) => { - console.log(provider, bucket); - - setCurrentBucketInfo({ - bucket: bucket, - custom_domain: provider.buckets.find((b) => b.bucket === bucket) - ?.custom_domain, - prefix: provider.buckets.find((b) => b.bucket === bucket)?.prefix, - platform: provider.platform, - channel: provider.channel, - provider_name: provider.provider_name, - public: true, - }); + const new_bucket = provider.buckets.find((b) => b.bucket === bucket); + if (new_bucket) { + setCurrentBucketInfo({ + bucket: bucket, + custom_domain: new_bucket.custom_domain, + prefix: new_bucket.prefix, + platform: provider.platform, + channel: provider.channel, + provider_name: provider.provider_name, + public: true, + file_size: new_bucket.file_size, + max_files: new_bucket.max_files, + max_storage: new_bucket.max_storage, + }); + } }; const handleSelectAllFiles = () => { @@ -257,15 +310,15 @@ export default function UserFileManager({ user, action }: FileListProps) { />
{/* Storage */} - {files && files.totalSize > 0 && plan && ( + {bucketUsage?.bucket && ( - +
} > - + )} {/* Bucket Select */} @@ -310,9 +363,11 @@ export default function UserFileManager({ user, action }: FileListProps) { {!isLoading && s3Configs && s3Configs.length > 0 && - currentBucketInfo && ( + currentBucketInfo && + bucketUsage && ( 0 ? Math.min((totalSize / maxSize) * 100, 100) : 0; + const bucketName = bucketUsage?.bucket || ""; const getStatusColor = (percentage) => { if (percentage >= 90) return "text-red-600"; @@ -27,6 +29,31 @@ export function FileSizeDisplay({ files, plan, t }) { return ; }; + const getStatusText = (percentage) => { + if (percentage >= 90) return t("storageFull"); + if (percentage >= 70) return t("storageHigh"); + return t("storageGood"); + }; + + // 处理无数据或异常情况 + if (!bucketUsage || maxSize <= 0) { + return ( +
+
+ +

+ {t("storageUsage")} +

+
+
+ + {t("storageDataUnavailable")} + +
+
+ ); + } + return (
{/* 标题 */} @@ -41,7 +68,7 @@ export function FileSizeDisplay({ files, plan, t }) {
- {t("used")} + {bucketName ? `${bucketName}` : t("storageQuota")} {usagePercentage.toFixed(1)}% @@ -56,53 +83,58 @@ export function FileSizeDisplay({ files, plan, t }) {
{/* 详细信息 */} -
-
- +
+
+ {t("usedSpace")}: - + {formatFileSize(totalSize, { precision: 2 })}
-
- +
+ {t("totalCapacity")}: - + {formatFileSize(maxSize, { precision: 2 })}
-
- +
+ {t("availableSpace")}: - + {formatFileSize(maxSize - totalSize, { precision: 2 })}
+ {bucketUsage?.usage?.totalFiles !== undefined && ( +
+ + {t("totalFiles")}: + + + {bucketUsage.usage.totalFiles.toLocaleString()} /{" "} + {nFormatter(bucketUsage.limits.maxFiles)} + +
+ )}
{/* 状态提示 */}
{getStatusIcon(usagePercentage)} - - {usagePercentage >= 90 - ? t("storageFull") - : usagePercentage >= 70 - ? t("storageHigh") - : t("storageGood")} - + {getStatusText(usagePercentage)}
); } -export function CircularStorageIndicator({ files, plan, size = 32 }) { - const totalSize = files?.totalSize || 0; - const maxSize = Number(plan?.stMaxTotalSize || 0); +export function CircularStorageIndicator({ bucketUsage, size = 32 }) { + const totalSize = bucketUsage?.usage?.totalSize || 0; + const maxSize = bucketUsage?.limits?.maxStorage || 0; const usagePercentage = maxSize > 0 ? Math.min((totalSize / maxSize) * 100, 100) : 0; @@ -119,6 +151,20 @@ export function CircularStorageIndicator({ files, plan, size = 32 }) { return "#3b82f6"; // blue-500 }; + // 处理无数据情况 + if (!bucketUsage || maxSize <= 0) { + return ( +
+ + - + +
+ ); + } + return (
void; }) => { const t = useTranslations("Components"); @@ -98,11 +99,11 @@ export const FileUploader = ({
{t("Limit")}:{" "} - {formatFileSize(Number(plan?.stMaxFileSize || "0"), { - precision: 0, + {formatFileSize(bucketUsage.limits.maxSingleFileSize, { + precision: 1, })}{" "} /{" "} - {formatFileSize(Number(plan?.stMaxTotalSize || "0"), { + {formatFileSize(bucketUsage.limits.maxStorage, { precision: 0, })} @@ -257,27 +258,36 @@ export const FileUploader = ({ ) : ( (file.status === "error" || file.status === "cancelled") && ( -
-
-
- {t("Aborted")} +
+
+
+
+ {file.status === "cancelled" + ? t("Aborted") + : t("Failed")} +
+ +
- - + {file.status === "error" && file.error && ( +
+ {file.error} +
+ )}
) )} diff --git a/components/forms/plan-form.tsx b/components/forms/plan-form.tsx index 305d724..81a995c 100644 --- a/components/forms/plan-form.tsx +++ b/components/forms/plan-form.tsx @@ -407,11 +407,10 @@ export function PlanForm({
-
+ {/*

{t("Storage Service")}

- {/* Max File Size - stMaxFileSize */}
- {/* Max File Size - stMaxTotalSize */}
-
+
*/} {/* Action buttons */}
diff --git a/hooks/use-file-upload.ts b/hooks/use-file-upload.ts index c0302fd..f938484 100644 --- a/hooks/use-file-upload.ts +++ b/hooks/use-file-upload.ts @@ -146,7 +146,25 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) { }); if (!response.ok) { - throw new Error("获取预签名 URL 失败"); + // 尝试获取后端返回的具体错误信息 + let errorMessage = "获取预签名 URL 失败"; + try { + const errorText = await response.text(); + if (errorText) { + // 如果返回的是JSON格式的错误信息,尝试解析 + try { + const errorData = JSON.parse(errorText); + errorMessage = errorData.message || errorData.error || errorText; + } catch { + // 如果不是JSON,直接使用文本内容 + errorMessage = errorText; + } + } + } catch { + // 如果无法读取响应内容,使用默认错误信息 + errorMessage = `上传失败 (${response.status})`; + } + throw new Error(errorMessage); } const data = await response.json(); @@ -331,14 +349,15 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) { await Promise.allSettled(uploadPromises); } catch (error) { console.error("上传失败:", error); - // 将所有 pending 状态的文件设置为错误状态 + // 将所有 pending 状态的文件设置为错误状态,并显示具体错误信息 + const errorMessage = error instanceof Error ? error.message : "上传失败"; setFiles((prev) => prev.map((file) => file.status === "pending" ? { ...file, status: "error", - error: "上传失败", + error: errorMessage, } : file, ), diff --git a/lib/dto/files.ts b/lib/dto/files.ts index df7b65c..df3fddd 100644 --- a/lib/dto/files.ts +++ b/lib/dto/files.ts @@ -159,18 +159,22 @@ export async function getUserFiles(options: QueryUserFileOptions = {}) { prisma.userFile.count({ where }), prisma.userFile.aggregate({ where: { - // bucket, - // providerName, + bucket, + providerName, status: 1, ...(userId && { userId }), }, _sum: { size: true }, + _count: { + id: true, + }, }), ]); return { total, totalSize: storageValueToBytes(totalSize._sum.size || 0), + totalFiles: totalSize._count.id || 0, list: files, }; } catch (error) { @@ -276,7 +280,7 @@ export async function getUserFileStats(userId: string) { success: true, data: { totalFiles, - totalSize: totalSize._sum.size || 0, + totalSize: storageValueToBytes(totalSize._sum.size || 0), filesByProvider, }, }; @@ -359,3 +363,41 @@ export async function cleanupExpiredFiles(days: number = 30) { return { success: false, error: "Failed to clean up expired files" }; } } + +// 获取特定存储桶的使用量统计 +export async function getBucketStorageUsage( + bucket: string, + providerName: string, + userId?: string, +): Promise< + | { success: true; data: { totalSize: number; totalFiles: number } } + | { success: false; error: string } +> { + try { + const result = await prisma.userFile.aggregate({ + where: { + ...(userId && { userId }), + bucket, + providerName, + status: 1, + }, + _sum: { + size: true, + }, + _count: { + id: true, + }, + }); + + return { + success: true, + data: { + totalSize: storageValueToBytes(result._sum.size || 0), + totalFiles: result._count.id || 0, + }, + }; + } catch (error) { + console.error("Failed to get bucket storage usage:", error); + return { success: false, error: "Failed to get bucket storage usage" }; + } +} diff --git a/lib/r2.ts b/lib/s3.ts similarity index 94% rename from lib/r2.ts rename to lib/s3.ts index 3aa3b98..adda389 100644 --- a/lib/r2.ts +++ b/lib/s3.ts @@ -32,8 +32,10 @@ export interface BucketItem { bucket: string; custom_domain?: string; prefix?: string; - file_types?: string; - file_size?: string; + file_types?: string; // 允许上传的文件类型 + file_size?: string; // 单个文件限制(字节) + max_files?: string; // 存储桶最大文件数量 + max_storage?: string; // 存储桶最大存储容量(字节) region?: string; public: boolean; } diff --git a/lib/validations/record.ts b/lib/validations/record.ts index 757f75d..e1e7f0c 100644 --- a/lib/validations/record.ts +++ b/lib/validations/record.ts @@ -12,8 +12,8 @@ export const createRecordSchema = z.object({ .string() .regex(/^[a-zA-Z0-9-_]+$/, "Invalid characters") .min(1) - .max(32), - content: z.string().min(1).max(32), + .max(64), + content: z.string().min(1).max(1024), ttl: z.number().min(1).max(36000).default(1), proxied: z.boolean().default(false), comment: z.string().optional(), diff --git a/locales/en.json b/locales/en.json index 73ec486..6fecc0e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,9 +211,17 @@ "usedSpace": "Used Space", "totalCapacity": "Total Capacity", "availableSpace": "Available Space", + "totalFiles": "Total Files", + "storageDataUnavailable": "Storage data is unavailable", "storageFull": "Storage space is almost full", "storageHigh": "Storage space usage is high", "storageGood": "Storage space is sufficient", + "planQuota": "Plan Quota", + "planUsagePercentage": "Usage Percentage", + "bucketQuota": "Bucket Quota", + "bucketCapacity": "Bucket Capacity", + "bucketStorageFull": "Bucket storage is almost full", + "bucketStorageHigh": "Bucket storage usage is high", "items": "items", "Total": "Total", "Configuration Error": "Configuration Error" @@ -360,6 +368,7 @@ "Uploading": "Uploading", "Completed": "Completed", "Aborted": "Aborted", + "Failed": "Failed", "Drop files to upload them to": "Drop files to upload them to", "Drag and drop file(s) here": "Drag and drop file(s) here", "or": "or", @@ -623,6 +632,8 @@ "Region": "Region", "Prefix": "Prefix", "Optional": "Optional", + "Max Storage": "Max Storage", + "maxStorageTooltip": "Set maximum storage capacity for this bucket (in bytes). If not set, uses plan quota global limit.", "Allowed File Types": "Allowed File Types", "Public": "Public", "Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket", @@ -632,6 +643,7 @@ "Unique": "Unique", "Add Provider": "Add Provider", "{length} Buckets": "{length} Buckets", - "Save Modifications": "Save Modifications" + "Save Modifications": "Save Modifications", + "Max File Count": "Max File Count" } } diff --git a/locales/zh.json b/locales/zh.json index 237aa1e..9bd9c9d 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -211,9 +211,17 @@ "usedSpace": "已使用空间", "totalCapacity": "总容量", "availableSpace": "剩余空间", + "totalFiles": "总文件数", + "storageDataUnavailable": "无数据", "storageFull": "存储空间即将用完", "storageHigh": "存储空间使用较多", "storageGood": "存储空间充足", + "planQuota": "计划配额", + "planUsagePercentage": "使用率", + "bucketQuota": "存储桶配额", + "bucketCapacity": "存储桶容量", + "bucketStorageFull": "存储桶空间即将用完", + "bucketStorageHigh": "存储桶空间使用较多", "items": "条", "Total": "共", "Configuration Error": "配置错误" @@ -360,6 +368,7 @@ "Uploading": "上传中", "Completed": "已完成", "Aborted": "已中止", + "Failed": "失败", "Drop files to upload them to": "将文件上传到", "Drag and drop file(s) here": "将文件拖到此处上传", "or": "或", @@ -620,9 +629,12 @@ "Bucket Name": "存储桶名称", "Public Domain": "公开域名或自定义域名", "Max File Size": "上传文件大小限制", + "Max File Count": "文件数量限制", "Region": "存储桶区域", "Prefix": "前缀", "Optional": "可选", + "Max Storage": "最大存储容量", + "maxStorageTooltip": "设置此存储桶的最大存储容量(字节)。如果不设置,默认使用 Plan 配额的全局限制。", "Allowed File Types": "允许的文件类型", "Public": "公开", "Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "公开此存储桶,所有注册用户都可以上传文件到此存储桶; 若不公开,只有管理员可以上传文件到此存储桶", diff --git a/prisma/migrations/20250714192022/migration.sql b/prisma/migrations/20250714192022/migration.sql index a4e29db..a3ef8c5 100644 --- a/prisma/migrations/20250714192022/migration.sql +++ b/prisma/migrations/20250714192022/migration.sql @@ -9,7 +9,7 @@ INSERT INTO "system_configs" VALUES ( 's3_config_list', - '[{"enabled":true,"platform":"cloudflare","channel":"r2","provider_name":"Cloudflare R2","account_id":"","access_key_id":"","secret_access_key":"","endpoint":"https://.r2.cloudflarestorage.com","buckets":[{"bucket":"","prefix":"","file_types":"","region":"auto","custom_domain":"","file_size":"26214400","public":true}]},{"enabled":false,"platform":"tencent","channel":"cos","provider_name":"腾讯云 COS","endpoint":"","account_id":"","access_key_id":"","secret_access_key":"","buckets":[{"custom_domain":"","prefix":"","bucket":"","file_types":"","file_size":"26214400","region":"","public":true}]}]', + '[{"enabled":true,"platform":"cloudflare","channel":"r2","provider_name":"Cloudflare R2","account_id":"","access_key_id":"","secret_access_key":"","endpoint":"https://.r2.cloudflarestorage.com","buckets":[{"bucket":"","prefix":"","file_types":"","region":"auto","custom_domain":"","file_size":"26214400","max_storage":"1073741824","max_files":"1000","public":true}]},{"enabled":false,"platform":"tencent","channel":"cos","provider_name":"腾讯云 COS","endpoint":"","account_id":"","access_key_id":"","secret_access_key":"","buckets":[{"custom_domain":"","prefix":"","bucket":"","file_types":"","file_size":"26214400","max_storage":"1073741824","max_files":"1000","region":"","public":true}]}]', 'OBJECT', 'R2 存储桶配置' ); \ No newline at end of file diff --git a/public/sw.js.map b/public/sw.js.map index 5e7ac94..88022f8 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/9ab3eac97b468a12fb965afe827f5191/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/3660dc403c2d3ab798169bbe82eb20c0/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAa,EAEZ,CAAA;EAQDC,CAAI,CAAA,CAAA,CAAA,CAACC,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAE,CAAA;AAElBC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAyB,EAAE,CAAA;AAI3BC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,CAAA,CAAA,CAAG,CAAE,CAAA,CAAA,CAAA,CAAA,CAAIC,oBAA+B,CAAC,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,EAAC,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAO,EAAE,CAAC,CAAA;GAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAe,EAAE,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;AAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA;AAAM,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAIF,QAAQ,CAAIA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAQ,CAACG,CAAI,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,gBAAgB,CAAE,CAAA,CAAA;AAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,OAAO,CAAIC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAQ,CAACJ,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAACK,IAAI,CAAE,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAM,EAAE,CAAG,CAAA,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAU,EAAE,CAAI,CAAA,CAAA,CAAA,CAAA;YAAEC,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAER,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAACQ,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA;AAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAC,CAAC,CAAA;EAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAOR,QAAQ,CAAA;EAAC,CAAA,CAAA,CAAA,CAAA,CAAA;KAAG,CAAA;AAAE,CAAA,CAAA,CAAC,CAAC,CAAA,CAAE,CAAK,CAAA,CAAA,CAAA,CAAA,CAAC,CAAA;AACxWL,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,CAAA,CAAA,CAAA,CAAA,CAAK,CAAE,CAAA,CAAA,CAAA,CAAA,CAAIc,mBAA8B,CAAC,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,EAAC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;EAAEZ,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAO,EAAE,CAAA,CAAA;EAAG,CAAC,CAAC,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,CAAC,CAAA;;"} \ No newline at end of file