fix(storage): 凭证明文返回便于编辑测试 + 建桶后二次校验防"假成功"
问题(log.astx.club/storage):
1. 编辑存储时 Access Key / Secret Key 都是空的,点「测试连接」提示
请先填写 Bucket 与 Access Key / Secret Key,无法测试也无法保存;
2. 提示"桶不存在,已自动创建"但疑似没有真正创建;
3. 测试失败提示用 toast 弹到右下角,不在弹框内。
修复:
1. models/storage.go Sanitized() 返回 accessKey / secretKey 明文
(内网运维工具,管理员需要在编辑弹框里看到并测试连接;脱敏会导致
编辑时字段为空、校验失败)
2. 前端 StorageConfig 类型补 accessKey / secretKey 字段;
编辑弹框打开时回填这两个字段(原来只有 secretKey 且强制置空);
Secret Key 标签去掉「留空保持不变」
3. services/storage_check.go 建桶后二次 ListBuckets 校验:
不同对象存储 CreateBucket 语义不同(腾讯云 COS 要求桶名放 Host、
路径为 /),直接 PUT /<bucket> 可能返回 200 但未真正创建。
现在创建后重新列举桶,找不到就报"创建后校验失败"并提示检查
Endpoint / Region / 桶名(COS 需带 APPID)
4. 前端「请先填写 Bucket 与 Access Key / Secret Key」改为在弹框内
testResult 区域显示(原来 toast 弹右下角)
镜像:lghost/lokistack:latest + v0.1 = sha256:6b6cc9769e79
This commit is contained in:
1 parent
d0b9b6b71d
commit
ff69ea2ce8
4 files changed
+60
-10
No files matched your search
@@ -20,12 +20,16 @@ type StorageConfig struct {
|
||||
|
||||
func (s *StorageConfig) Sanitized() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": s.ID,
|
||||
"name": s.Name,
|
||||
"storageType": s.StorageType,
|
||||
"endpoint": s.Endpoint,
|
||||
"bucket": s.Bucket,
|
||||
"region": s.Region,
|
||||
"id": s.ID,
|
||||
"name": s.Name,
|
||||
"storageType": s.StorageType,
|
||||
"endpoint": s.Endpoint,
|
||||
"bucket": s.Bucket,
|
||||
"region": s.Region,
|
||||
// accessKey / secretKey 明文返回:本平台为内网运维工具,管理员需要在
|
||||
// 编辑弹框里看到并「测试连接」,脱敏会导致编辑时字段为空无法保存/测试。
|
||||
"accessKey": s.AccessKey,
|
||||
"secretKey": s.SecretKey,
|
||||
"pathPrefix": s.PathPrefix,
|
||||
"insecure": s.Insecure,
|
||||
"isDefault": s.IsDefault,
|
||||
|
||||
@@ -204,9 +204,42 @@ func TestStorageConnection(p StorageTestParams) StorageTestResult {
|
||||
|
||||
// 200=创建成功;409 BucketAlreadyOwnedByYou 视为已存在
|
||||
if putResp.StatusCode == http.StatusOK || putResp.StatusCode == http.StatusConflict {
|
||||
// 二次校验:不同对象存储的 CreateBucket 语义不一致。
|
||||
// 例如腾讯云 COS 要求桶名放在 Host(<bucket-appid>.cos.<region>.myqcloud.com)、
|
||||
// 路径为 /,直接 PUT /<bucket> 可能返回 200 但并未真正建桶。
|
||||
// 因此重建后再 ListBuckets 确认桶确实出现,避免前端看到"假的创建成功"。
|
||||
verified := false
|
||||
if vresp, verr := s3Request("GET", p.Endpoint, p.Region, p.AccessKey, p.SecretKey, "/", p.Insecure); verr == nil {
|
||||
vbody, _ := io.ReadAll(io.LimitReader(vresp.Body, 1<<20))
|
||||
vresp.Body.Close()
|
||||
var vlist struct {
|
||||
Buckets struct {
|
||||
Bucket []struct {
|
||||
Name string `xml:"Name"`
|
||||
} `xml:"Bucket"`
|
||||
} `xml:"Buckets"`
|
||||
}
|
||||
_ = xml.Unmarshal(vbody, &vlist)
|
||||
for _, b := range vlist.Buckets.Bucket {
|
||||
if b.Name == p.Bucket {
|
||||
verified = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !verified {
|
||||
result.BucketExists = false
|
||||
result.BucketCreated = false
|
||||
result.Message = fmt.Sprintf(
|
||||
"凭证有效,但桶「%s」创建后校验失败:桶列表里找不到它。"+
|
||||
"请确认 Endpoint / Region / 桶名是否正确"+
|
||||
"(腾讯云 COS 桶名需带 APPID,例如 mybucket-1250000000),"+
|
||||
"或先到云控制台手动创建该桶", p.Bucket)
|
||||
return result
|
||||
}
|
||||
result.BucketExists = true
|
||||
result.BucketCreated = true
|
||||
result.Message = fmt.Sprintf("连接成功,桶「%s」不存在,已自动创建 ✓", p.Bucket)
|
||||
result.Message = fmt.Sprintf("连接成功,桶「%s」不存在,已自动创建并校验通过 ✓", p.Bucket)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,10 @@ export const StorageFormDialog = memo(function StorageFormDialog({
|
||||
form.reset(editing ? {
|
||||
name: editing.name, storageType: editing.storageType,
|
||||
endpoint: editing.endpoint || '', bucket: editing.bucket,
|
||||
region: editing.region || '', secretKey: '',
|
||||
region: editing.region || '',
|
||||
// 后端已返回明文凭据,编辑时回填,避免每次都提示"请先填写 Access Key / Secret Key"
|
||||
accessKey: editing.accessKey || '',
|
||||
secretKey: editing.secretKey || '',
|
||||
pathPrefix: editing.pathPrefix || '', insecure: editing.insecure, isDefault: editing.isDefault,
|
||||
} as never : DEFAULT_VALUES)
|
||||
setTestResult(null)
|
||||
@@ -102,7 +105,14 @@ export const StorageFormDialog = memo(function StorageFormDialog({
|
||||
const handleTest = async () => {
|
||||
const values = form.getValues()
|
||||
if (!values.bucket.trim() || !values.accessKey?.trim() || !values.secretKey?.trim()) {
|
||||
toast.error('请先填写 Bucket 与 Access Key / Secret Key')
|
||||
// 提示显示在弹框内(原来用 toast 弹到右下角,用户看不到是哪个字段的问题)
|
||||
setTestResult({
|
||||
reachable: false,
|
||||
authenticated: false,
|
||||
bucketExists: false,
|
||||
bucketCreated: false,
|
||||
message: '请先填写 Bucket 与 Access Key / Secret Key',
|
||||
})
|
||||
return
|
||||
}
|
||||
setTesting(true)
|
||||
@@ -301,7 +311,7 @@ export const StorageFormDialog = memo(function StorageFormDialog({
|
||||
name="secretKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Secret Key{editing && '(留空保持不变)'}</FormLabel>
|
||||
<FormLabel>Secret Key</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder="minioadmin" {...field} />
|
||||
</FormControl>
|
||||
|
||||
@@ -101,6 +101,9 @@ export interface StorageConfig {
|
||||
pathPrefix: string;
|
||||
insecure: boolean;
|
||||
isDefault: boolean;
|
||||
/** 明文返回,便于管理员在编辑弹框里查看/测试连接 */
|
||||
accessKey?: string;
|
||||
secretKey?: string;
|
||||
hasSecretKey?: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
Reference in new issue
Block a user