mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-25 03:29:13 +08:00
feat: 备份账号 OSS 、COS 支持存储类型选择 (#1464)
This commit is contained in:
parent
d44231a0ff
commit
27918e2dc5
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ import (
|
||||
)
|
||||
|
||||
type s3Client struct {
|
||||
Vars map[string]interface{}
|
||||
Sess session.Session
|
||||
scType string
|
||||
Vars map[string]interface{}
|
||||
Sess session.Session
|
||||
}
|
||||
|
||||
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,8 +60,9 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &s3Client{
|
||||
Vars: vars,
|
||||
Sess: *sess,
|
||||
scType: scType,
|
||||
Vars: vars,
|
||||
Sess: *sess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -126,9 +134,10 @@ func (s3C s3Client) Upload(src, target string) (bool, error) {
|
||||
|
||||
uploader := s3manager.NewUploader(&s3C.Sess)
|
||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(target),
|
||||
Body: file,
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(target),
|
||||
Body: file,
|
||||
StorageClass: &s3C.scType,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -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> ",
|
||||
|
@ -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> ",
|
||||
|
@ -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(),
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user