feat: 备份账号 OSS 、COS 支持存储类型选择 (#1464)

This commit is contained in:
ssongliu 2023-06-27 18:57:59 +08:00 committed by GitHub
parent d44231a0ff
commit 27918e2dc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 189 additions and 21 deletions

View File

@ -133,7 +133,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
}
if backup.Type == "LOCAL" || record.FromLocal {
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", constant.ErrRecordNotFound
return "", err
}
return record.File, nil
}
@ -145,7 +145,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", constant.ErrRecordNotFound
return "", err
}
return tempPath, nil
}

View File

@ -14,6 +14,7 @@ type cosClient struct {
region string
accessKey string
secretKey string
scType string
Vars map[string]interface{}
client *cosSDK.Client
}
@ -21,6 +22,7 @@ type cosClient struct {
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
var accessKey string
var secretKey string
var scType string
var region string
if _, ok := vars["region"]; ok {
region = vars["region"].(string)
@ -32,6 +34,11 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string)
} else {
@ -47,7 +54,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
},
})
return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, region: region}, nil
return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, scType: scType, region: region}, nil
}
func (cos cosClient) ListBuckets() ([]interface{}, error) {
@ -90,7 +97,12 @@ func (cos cosClient) Upload(src, target string) (bool, error) {
if err != nil {
return false, err
}
if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{}); err != nil {
if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{
ACLHeaderOptions: nil,
ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{
XCosStorageClass: cos.scType,
},
}); err != nil {
return false, err
}
return true, nil

View File

@ -6,6 +6,7 @@ import (
)
type ossClient struct {
scType string
Vars map[string]interface{}
client osssdk.Client
}
@ -14,6 +15,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
var endpoint string
var accessKey string
var secretKey string
var scType string
if _, ok := vars["endpoint"]; ok {
endpoint = vars["endpoint"].(string)
} else {
@ -24,6 +26,11 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string)
} else {
@ -34,6 +41,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
return nil, err
}
return &ossClient{
scType: scType,
Vars: vars,
client: *client,
}, nil
@ -77,7 +85,7 @@ func (oss ossClient) Upload(src, target string) (bool, error) {
if err != nil {
return false, err
}
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""))
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""), osssdk.ObjectStorageClass(osssdk.StorageClassType(oss.scType)))
if err != nil {
return false, err
}

View File

@ -13,6 +13,7 @@ import (
)
type s3Client struct {
scType string
Vars map[string]interface{}
Sess session.Session
}
@ -21,6 +22,7 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
var accessKey string
var secretKey string
var endpoint string
var scType string
var region string
if _, ok := vars["accessKey"]; ok {
accessKey = vars["accessKey"].(string)
@ -32,6 +34,11 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["endpoint"]; ok {
endpoint = vars["endpoint"].(string)
} else {
@ -53,6 +60,7 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
return nil, err
}
return &s3Client{
scType: scType,
Vars: vars,
Sess: *sess,
}, nil
@ -129,6 +137,7 @@ func (s3C s3Client) Upload(src, target string) (bool, error) {
Bucket: aws.String(bucket),
Key: aws.String(target),
Body: file,
StorageClass: &s3C.scType,
})
if err != nil {
return false, err

View File

@ -997,6 +997,19 @@ const message = {
loadCode: 'Acquire',
COS: 'Tencent COS',
KODO: 'Qiniu Kodo',
scType: ' Storage type',
typeStandard: 'Standard',
typeStandard_IA: 'Standard_IA',
typeArchive: 'Archive',
typeDeep_Archive: 'Deep_Archive',
scStandard:
'Standard Storage is suitable for business scenarios with a large number of hot files that require real-time access, frequent data interaction, and so on.',
scStandard_IA:
'Low-frequency storage is suitable for business scenarios with relatively low access frequency, and stores data for at least 30 days.',
scArchive: 'Archival storage is suitable for business scenarios with extremely low access frequency.',
scDeep_Archive: 'Durable cold storage is suitable for business scenarios with extremely low access frequency.',
archiveHelper:
'Archival storage files cannot be downloaded directly and must first be restored through the corresponding cloud service provider`s website. Please use with caution!',
domainHelper: 'The accelerated domain name must contain http:// or https://',
backupAlert:
"In theory, as long as the cloud provider is compatible with the S3 protocol, existing Amazon S3 cloud storage can be used for backup. For specific configurations, please refer to the <a target=“_blank” href='https://1panel.cn/docs/user_manual/settings/#3'>official documentation.</a> ",

View File

@ -975,6 +975,16 @@ const message = {
loadCode: '获取',
COS: '腾讯云 COS',
KODO: '七牛云 Kodo',
scType: '存储类型',
typeStandard: '标准存储',
typeStandard_IA: '低频存储',
typeArchive: '归档存储',
typeDeep_Archive: '深度归档存储',
scStandard: '标准存储适用于实时访问的大量热点文件频繁的数据交互等业务场景',
scStandard_IA: '低频存储适用于较低访问频率例如平均每月访问频率1到2次的业务场景最少存储30天',
scArchive: '归档存储适用于极低访问频率例如半年访问1次的业务场景',
scDeep_Archive: '深度归档存储适用于极低访问频率例如1年访问1~2的业务场景',
archiveHelper: '归档存储的文件无法直接下载需要先在对应的云服务商网站进行恢复操作请谨慎使用',
domainHelper: '加速域名必须包含 http:// 或者 https://',
backupAlert:
"理论上只要云厂商兼容 S3 协议,就可以用现有的亚马逊 S3 云存储来备份,具体配置参考 <a target=“_blank” href='https://1panel.cn/docs/user_manual/settings/#3'>官方文档</a> ",

View File

@ -66,6 +66,20 @@
<el-form-item label="Bucket">
{{ s3Data.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!s3Data.varsJson['scType'] || s3Data.varsJson['scType'] === 'STANDARD'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'STANDARD_IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'GLACIER'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'DEEP_ARCHIVE'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ s3Data.backupPath }}
</el-form-item>
@ -73,7 +87,7 @@
{{ dateFormat(0, 0, s3Data.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'S3')">
{{ $t('setting.createBackupAccount', [$t('setting.S3')]) }}
</el-button>
@ -105,6 +119,20 @@
<el-form-item label="Bucket">
{{ ossData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!ossData.varsJson['scType'] || ossData.varsJson['scType'] === 'Standard'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'Archive'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'ColdArchive'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ ossData.backupPath }}
</el-form-item>
@ -112,7 +140,7 @@
{{ dateFormat(0, 0, ossData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'OSS')">
{{ $t('setting.createBackupAccount', [$t('setting.OSS')]) }}
</el-button>
@ -145,6 +173,20 @@
<el-form-item label="Bucket">
{{ cosData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!cosData.varsJson['scType'] || cosData.varsJson['scType'] === 'Standard'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Standard_IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Archive'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Deep_Archive'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ cosData.backupPath }}
</el-form-item>
@ -152,7 +194,7 @@
{{ dateFormat(0, 0, cosData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'COS')">
{{ $t('setting.createBackupAccount', [$t('setting.COS')]) }}
</el-button>
@ -185,7 +227,7 @@
{{ dateFormat(0, 0, oneDriveData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button
size="large"
round
@ -232,7 +274,7 @@
{{ dateFormat(0, 0, kodoData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'KODO')">
{{ $t('setting.createBackupAccount', [$t('setting.KODO')]) }}
</el-button>
@ -270,7 +312,7 @@
{{ dateFormat(0, 0, minioData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'MINIO')">
{{ $t('setting.createBackupAccount', [$t('setting.MINIO')]) }}
</el-button>
@ -314,7 +356,7 @@
{{ dateFormat(0, 0, sftpData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onOpenDialog('create', 'SFTP')">
{{ $t('setting.createBackupAccount', [$t('setting.SFTP')]) }}
</el-button>
@ -361,6 +403,7 @@ const ossData = ref<Backup.BackupInfo>({
varsJson: {
region: '',
endpoint: '',
scType: 'Standard',
},
createdAt: new Date(),
});
@ -415,6 +458,7 @@ const s3Data = ref<Backup.BackupInfo>({
vars: '',
varsJson: {
region: '',
scType: 'Standard',
endpoint: '',
},
createdAt: new Date(),
@ -429,6 +473,7 @@ const cosData = ref<Backup.BackupInfo>({
vars: '',
varsJson: {
region: '',
scType: 'Standard',
},
createdAt: new Date(),
});

View File

@ -117,6 +117,66 @@
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'COS'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="Standard_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="Deep_Archive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'Archive' || dialogData.rowData!.varsJson['scType'] === 'Deep_Archive'"
style="margin-top: 10px"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'OSS'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="ColdArchive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'Archive' || dialogData.rowData!.varsJson['scType'] === 'ColdArchive'"
style="margin-top: 10px"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'S3'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="STANDARD" :label="$t('setting.scStandard')" />
<el-option value="STANDARD_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="GLACIER" :label="$t('setting.scArchive')" />
<el-option value="DEEP_ARCHIVE" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'Archive' || dialogData.rowData!.varsJson['scType'] === 'ColdArchive'"
style="margin-top: 10px"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<div v-if="dialogData.rowData!.type === 'SFTP'">
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.host">
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
@ -156,14 +216,14 @@
:label="$t('setting.backupDir')"
prop="backupPath"
>
<el-input clearable v-model.trim="dialogData.rowData!.backupPath" />
<el-input clearable v-model.trim="dialogData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="drawerVisiable = false">
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
@ -220,11 +280,22 @@ const acceptParams = (params: DialogProps): void => {
if (dialogData.value.title === 'create' && dialogData.value.rowData!.type === 'SFTP') {
dialogData.value.rowData.varsJson['port'] = 22;
}
if (dialogData.value.rowData!.type === 'COS' || dialogData.value.rowData!.type === 'OSS') {
if (params.title === 'create' || (params.title === 'edit' && !dialogData.value.rowData.varsJson['scType'])) {
dialogData.value.rowData.varsJson['scType'] = 'Standard';
}
}
if (dialogData.value.rowData!.type === 'S3') {
if (params.title === 'create' || (params.title === 'edit' && !dialogData.value.rowData.varsJson['scType'])) {
dialogData.value.rowData.varsJson['scType'] = 'STANDARD';
}
}
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisiable.value = true;
};
const handleClose = () => {
emit('search');
drawerVisiable.value = false;
};