mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
fix: bucket name can not contain uppercase letters rustfs/rustfs#2636
This commit is contained in:
@@ -4,13 +4,14 @@ import * as React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Field, FieldContent, FieldLabel } from "@/components/ui/field"
|
||||
import { Field, FieldContent, FieldDescription, FieldLabel } from "@/components/ui/field"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { useBucket } from "@/hooks/use-bucket"
|
||||
import { usePermissions } from "@/hooks/use-permissions"
|
||||
import { getOptionalBucketNameError } from "@/lib/bucket-name"
|
||||
import { getBytes } from "@/lib/functions"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@@ -48,9 +49,9 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
|
||||
const [creating, setCreating] = React.useState(false)
|
||||
|
||||
const trimmedBucketName = objectKey.trim()
|
||||
const showNameError = trimmedBucketName.length > 0 && (trimmedBucketName.length < 3 || trimmedBucketName.length > 63)
|
||||
const nameError = getOptionalBucketNameError(objectKey)
|
||||
const parsedRetentionPeriod = Math.max(1, Number.parseInt(retentionPeriod, 10) || 1)
|
||||
const isSubmitDisabled = creating || trimmedBucketName.length < 3 || trimmedBucketName.length > 63
|
||||
const isSubmitDisabled = creating || !trimmedBucketName || !!nameError
|
||||
const canCreateBucket = canCapability("bucket.create")
|
||||
const bucketContext = trimmedBucketName ? { bucket: trimmedBucketName } : {}
|
||||
const canEditVersioning = canCapability("bucket.versioning.edit", bucketContext)
|
||||
@@ -85,6 +86,10 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
|
||||
|
||||
const handleCreateBucket = async () => {
|
||||
if (!canCreateBucket || isSubmitDisabled) return
|
||||
if (nameError) {
|
||||
message.error(t(nameError))
|
||||
return
|
||||
}
|
||||
|
||||
const bucketName = trimmedBucketName
|
||||
setCreating(true)
|
||||
@@ -161,9 +166,10 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
|
||||
value={objectKey}
|
||||
onChange={(e) => setObjectKey(e.target.value)}
|
||||
autoComplete="off"
|
||||
className={cn("w-full", showNameError && "border-destructive focus-visible:ring-destructive")}
|
||||
className={cn("w-full", nameError && "border-destructive focus-visible:ring-destructive")}
|
||||
/>
|
||||
</FieldContent>
|
||||
{nameError && <FieldDescription className="text-destructive">{t(nameError)}</FieldDescription>}
|
||||
</Field>
|
||||
|
||||
<Field orientation="responsive" className="items-center">
|
||||
|
||||
+12
-1
@@ -40,6 +40,7 @@ import {
|
||||
} from "@aws-sdk/client-s3"
|
||||
import { useS3 } from "@/contexts/s3-context"
|
||||
import { useApi } from "@/contexts/api-context"
|
||||
import { getBucketNameError } from "@/lib/bucket-name"
|
||||
|
||||
const BUCKETS_CACHE_DURATION = 10000
|
||||
let listBucketsCache: ListBucketsCommandOutput | null = null
|
||||
@@ -97,7 +98,17 @@ export function useBucket() {
|
||||
|
||||
const createBucket = useCallback(
|
||||
async (params: { Bucket: string; ObjectLockEnabledForBucket?: boolean }) => {
|
||||
const result = await client.send(new CreateBucketCommand(params))
|
||||
const nameError = getBucketNameError(params.Bucket)
|
||||
if (nameError) {
|
||||
throw new Error(nameError)
|
||||
}
|
||||
|
||||
const result = await client.send(
|
||||
new CreateBucketCommand({
|
||||
...params,
|
||||
Bucket: params.Bucket.trim(),
|
||||
}),
|
||||
)
|
||||
invalidateBucketsCache()
|
||||
return result
|
||||
},
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
"Bucket Policy": "Bucket Policy",
|
||||
"Bucket Quota": "Bucket Quota",
|
||||
"Bucket Quota Exceeded": "Bucket Quota Exceeded",
|
||||
"Bucket names must be 3-63 characters long": "Bucket names must be 3-63 characters long",
|
||||
"Bucket names must not contain uppercase letters": "Bucket names must not contain uppercase letters",
|
||||
"Quota Size": "Quota Size",
|
||||
"Bucket Quota Insufficient": "Bucket Quota Insufficient",
|
||||
"Quota Warning Content": "The total size of the selected files is {total}, which exceeds the remaining bucket quota {remaining}. Do you want to continue?",
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
"Bucket Quota": "存储桶配额",
|
||||
"Bucket Quota Exceeded": "存储桶配额已超出",
|
||||
"Bucket Quota Insufficient": "存储桶配额不足",
|
||||
"Bucket names must be 3-63 characters long": "存储桶名称长度必须为 3-63 个字符",
|
||||
"Bucket names must not contain uppercase letters": "存储桶名称不能包含大写字母",
|
||||
"Set Bucket CORS": "设置存储桶跨域配置",
|
||||
"Bucket Replication": "存储桶复制",
|
||||
"Bucket Setting": "存储桶设置",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const BUCKET_NAME_MIN_LENGTH = 3
|
||||
const BUCKET_NAME_MAX_LENGTH = 63
|
||||
const UPPERCASE_BUCKET_NAME_PATTERN = /[A-Z]/
|
||||
|
||||
export function getBucketNameError(name: string): string | null {
|
||||
const trimmedName = name.trim()
|
||||
|
||||
if (trimmedName.length < BUCKET_NAME_MIN_LENGTH || trimmedName.length > BUCKET_NAME_MAX_LENGTH) {
|
||||
return "Bucket names must be 3-63 characters long"
|
||||
}
|
||||
|
||||
if (UPPERCASE_BUCKET_NAME_PATTERN.test(trimmedName)) {
|
||||
return "Bucket names must not contain uppercase letters"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function getOptionalBucketNameError(name: string): string | null {
|
||||
if (!name.trim()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getBucketNameError(name)
|
||||
}
|
||||
|
||||
export function isBucketNameValid(name: string): boolean {
|
||||
return getBucketNameError(name) === null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import { getBucketNameError, getOptionalBucketNameError, isBucketNameValid } from "../../lib/bucket-name"
|
||||
|
||||
test("isBucketNameValid accepts lowercase bucket names", () => {
|
||||
assert.equal(isBucketNameValid("bucket-demo"), true)
|
||||
})
|
||||
|
||||
test("getBucketNameError rejects uppercase letters", () => {
|
||||
assert.equal(getBucketNameError("Bucket-Demo"), "Bucket names must not contain uppercase letters")
|
||||
})
|
||||
|
||||
test("getBucketNameError rejects names shorter than 3 characters", () => {
|
||||
assert.equal(getBucketNameError("ab"), "Bucket names must be 3-63 characters long")
|
||||
})
|
||||
|
||||
test("getOptionalBucketNameError ignores empty input", () => {
|
||||
assert.equal(getOptionalBucketNameError(" "), null)
|
||||
})
|
||||
Reference in New Issue
Block a user