+
+
+
+
+
+
+
+
+ {t("maxStorageTooltip")}
+
+
+
+
+
+
- updateBucket(index2, { max_storage: e.target.value })
+ updateBucket(index2, {
+ max_storage: e.target.value,
+ })
}
/>
{bucket.max_storage && (
- ≈{(Number(bucket.max_storage) / (1024 * 1024 * 1024)).toFixed(1)}GB
+ ≈{formatFileSize(Number(bucket.max_storage))}
)}
@@ -600,7 +644,7 @@ export default function S3Configs({}: {}) {
{t("How to get the S3 credentials?")}
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/bucket-usage/route.ts b/app/api/storage/bucket-usage/route.ts
deleted file mode 100644
index 2996f6c..0000000
--- a/app/api/storage/bucket-usage/route.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-
-import { getBucketStorageUsage } from "@/lib/dto/files";
-import { getMultipleConfigs } from "@/lib/dto/system-config";
-import { checkUserStatus } from "@/lib/dto/user";
-import { getCurrentUser } from "@/lib/session";
-
-export async function GET(request: NextRequest) {
- try {
- const user = checkUserStatus(await getCurrentUser());
- if (user instanceof Response) return user;
-
- const url = new URL(request.url);
- const bucket = url.searchParams.get("bucket");
- const provider = url.searchParams.get("provider");
-
- if (!bucket || !provider) {
- return NextResponse.json("Missing bucket or provider parameters", {
- status: 400,
- });
- }
-
- // 获取存储桶配置
- const configs = await getMultipleConfigs(["s3_config_list"]);
- if (!configs || !configs.s3_config_list) {
- return NextResponse.json("Invalid S3 configs", {
- status: 400,
- });
- }
-
- const providerConfig = configs.s3_config_list.find(
- (c) => c.provider_name === provider,
- );
- if (!providerConfig) {
- return NextResponse.json("Provider does not exist", {
- status: 400,
- });
- }
-
- const bucketConfig = providerConfig.buckets?.find(
- (b) => b.bucket === bucket,
- );
- if (!bucketConfig) {
- return NextResponse.json("Bucket does not exist", {
- status: 400,
- });
- }
-
- // 获取存储桶使用情况
- const bucketUsage = await getBucketStorageUsage(bucket, provider);
- if (!bucketUsage.success) {
- return NextResponse.json("Failed to get bucket usage", {
- status: 500,
- });
- }
-
- return NextResponse.json({
- bucket: bucket,
- provider: provider,
- usage: {
- totalSize: bucketUsage.data.totalSize,
- totalFiles: bucketUsage.data.totalFiles,
- },
- limits: {
- maxStorage: bucketConfig.max_storage ? Number(bucketConfig.max_storage) : null,
- },
- });
- } catch (error) {
- console.error("Error getting bucket usage:", error);
- return NextResponse.json({ error: "Server Error" }, { status: 500 });
- }
-}
\ No newline at end of file
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 c4258fc..0977017 100644
--- a/app/api/storage/s3/upload/route.ts
+++ b/app/api/storage/s3/upload/route.ts
@@ -3,12 +3,10 @@ import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { getBucketStorageUsage } from "@/lib/dto/files";
-import { getPlanQuota } from "@/lib/dto/plan";
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) {
@@ -45,38 +43,52 @@ 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,
+ });
+ }
}
}
- // 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 });
+ // 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 bucketConfig = buckets.find((b) => b.bucket === bucket);
- const totalUploadSize = files.reduce((sum, file) => sum + Number(file.size), 0);
-
+ const totalUploadSize = files.reduce(
+ (sum, file) => sum + Number(file.size),
+ 0,
+ );
+
if (bucketConfig?.max_storage) {
- const bucketUsage = await getBucketStorageUsage(bucket, provider);
+ 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);
+ const remainingSpaceGB = (
+ remainingSpace /
+ (1024 * 1024 * 1024)
+ ).toFixed(2);
return Response.json(
- `存储桶容量不足!剩余 ${remainingSpaceGB}GB,请更换存储桶`,
- { status: 403 }
+ `Bucket storage limit exceeded. Remaining space: ${remainingSpaceGB} GB.`,
+ { status: 403 },
);
}
}
diff --git a/components/file/index.tsx b/components/file/index.tsx
index 8702f39..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();
@@ -124,17 +142,6 @@ export default function UserFileManager({ user, action }: FileListProps) {
fetcher,
);
- const { data: bucketUsage } = useSWR(
- currentBucketInfo.bucket && currentBucketInfo.provider_name
- ? `/api/storage/bucket-usage?bucket=${currentBucketInfo.bucket}&provider=${currentBucketInfo.provider_name}`
- : null,
- fetcher,
- {
- revalidateOnFocus: false,
- refreshInterval: 30000, // 每30秒更新一次
- },
- );
-
useEffect(() => {
if (s3Configs && s3Configs.length > 0) {
setCurrentProvider(s3Configs[0]);
@@ -146,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(
@@ -162,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 = () => {
@@ -268,26 +310,15 @@ export default function UserFileManager({ user, action }: FileListProps) {
/>
{/* Storage */}
- {files && files.totalSize > 0 && plan && (
+ {bucketUsage?.bucket && (
-
+
)}
{/* Bucket Select */}
@@ -332,9 +363,11 @@ export default function UserFileManager({ user, action }: FileListProps) {
{!isLoading &&
s3Configs &&
s3Configs.length > 0 &&
- currentBucketInfo && (
+ currentBucketInfo &&
+ bucketUsage && (