mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 16:29:17 +08:00
feat: 拆分备份账号代码 (#3454)
This commit is contained in:
parent
b547955965
commit
ee2fa70bb0
@ -1239,6 +1239,28 @@ const message = {
|
|||||||
'Please click on the "Acquire" button, then login to OneDrive and copy the content after "code" in the redirected link. Paste it into this input box. For specific instructions, please refer to the official documentation.',
|
'Please click on the "Acquire" button, then login to OneDrive and copy the content after "code" in the redirected link. Paste it into this input box. For specific instructions, please refer to the official documentation.',
|
||||||
loadCode: 'Acquire',
|
loadCode: 'Acquire',
|
||||||
COS: 'Tencent COS',
|
COS: 'Tencent COS',
|
||||||
|
ap_beijing_1: 'Beijing Zone 1',
|
||||||
|
ap_beijing: 'Beijing',
|
||||||
|
ap_nanjing: 'Nanjing',
|
||||||
|
ap_shanghai: 'Shanghai',
|
||||||
|
ap_guangzhou: 'Guangzhou',
|
||||||
|
ap_chengdu: 'Chengdu',
|
||||||
|
ap_chongqing: 'Chongqing',
|
||||||
|
ap_shenzhen_fsi: 'Shenzhen Financial',
|
||||||
|
ap_shanghai_fsi: 'Shanghai Financial',
|
||||||
|
ap_beijing_fsi: 'Beijing Financial',
|
||||||
|
ap_hongkong: 'Hong Kong, China',
|
||||||
|
ap_singapore: 'Singapore',
|
||||||
|
ap_mumbai: 'Mumbai',
|
||||||
|
ap_jakarta: 'Jakarta',
|
||||||
|
ap_seoul: 'Seoul',
|
||||||
|
ap_bangkok: 'Bangkok',
|
||||||
|
ap_tokyo: 'Tokyo',
|
||||||
|
na_siliconvalley: 'Silicon Valley (US West)',
|
||||||
|
na_ashburn: 'Ashburn (US East)',
|
||||||
|
na_toronto: 'Toronto',
|
||||||
|
sa_saopaulo: 'Sao Paulo',
|
||||||
|
eu_frankfurt: 'Frankfurt',
|
||||||
KODO: 'Qiniu Kodo',
|
KODO: 'Qiniu Kodo',
|
||||||
scType: ' Storage type',
|
scType: ' Storage type',
|
||||||
typeStandard: 'Standard',
|
typeStandard: 'Standard',
|
||||||
@ -1253,7 +1275,6 @@ const message = {
|
|||||||
scDeep_Archive: 'Durable cold 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:
|
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!',
|
'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:
|
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> ",
|
"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> ",
|
||||||
domain: 'Accelerate domain',
|
domain: 'Accelerate domain',
|
||||||
|
@ -1165,6 +1165,28 @@ const message = {
|
|||||||
'請點擊獲取按鈕,然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。',
|
'請點擊獲取按鈕,然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。',
|
||||||
loadCode: '獲取',
|
loadCode: '獲取',
|
||||||
COS: '騰訊雲 COS',
|
COS: '騰訊雲 COS',
|
||||||
|
ap_beijing_1: '北京一區',
|
||||||
|
ap_beijing: '北京',
|
||||||
|
ap_nanjing: '南京',
|
||||||
|
ap_shanghai: '上海',
|
||||||
|
ap_guangzhou: '廣州',
|
||||||
|
ap_chengdu: '成都',
|
||||||
|
ap_chongqing: '重慶',
|
||||||
|
ap_shenzhen_fsi: '深圳金融',
|
||||||
|
ap_shanghai_fsi: '上海金融',
|
||||||
|
ap_beijing_fsi: '北京金融',
|
||||||
|
ap_hongkong: '中國香港',
|
||||||
|
ap_singapore: '新加坡',
|
||||||
|
ap_mumbai: '孟買',
|
||||||
|
ap_jakarta: '雅加達',
|
||||||
|
ap_seoul: '首爾',
|
||||||
|
ap_bangkok: '曼谷',
|
||||||
|
ap_tokyo: '東京',
|
||||||
|
na_siliconvalley: '硅谷(美西)',
|
||||||
|
na_ashburn: '弗吉尼亞(美東)',
|
||||||
|
na_toronto: '多倫多',
|
||||||
|
sa_saopaulo: '聖保羅',
|
||||||
|
eu_frankfurt: '法蘭克福',
|
||||||
KODO: '七牛雲 Kodo',
|
KODO: '七牛雲 Kodo',
|
||||||
scType: '存儲類型',
|
scType: '存儲類型',
|
||||||
typeStandard: '標準存儲',
|
typeStandard: '標準存儲',
|
||||||
@ -1176,7 +1198,6 @@ const message = {
|
|||||||
scArchive: '歸檔存儲,適用於極低訪問頻率(例如半年訪問1次)的業務場景。',
|
scArchive: '歸檔存儲,適用於極低訪問頻率(例如半年訪問1次)的業務場景。',
|
||||||
scDeep_Archive: '深度歸檔存儲,適用於極低訪問頻率(例如1年訪問1~2次)的業務場景。',
|
scDeep_Archive: '深度歸檔存儲,適用於極低訪問頻率(例如1年訪問1~2次)的業務場景。',
|
||||||
archiveHelper: '歸檔存儲的文件無法直接下載,需要先在對應的雲服務商網站進行恢復操作,請謹慎使用!',
|
archiveHelper: '歸檔存儲的文件無法直接下載,需要先在對應的雲服務商網站進行恢復操作,請謹慎使用!',
|
||||||
domainHelper: '加速域名必須包含 http:// 或者 https://',
|
|
||||||
backupAlert:
|
backupAlert:
|
||||||
"理論上只要雲廠商兼容 S3 協議,就可以用現有的亞馬遜 S3 雲存儲來備份,具體配置參考 <a target=「_blank」 href='https://1panel.cn/docs/user_manual/settings/#3'>官方文檔</a> ",
|
"理論上只要雲廠商兼容 S3 協議,就可以用現有的亞馬遜 S3 雲存儲來備份,具體配置參考 <a target=「_blank」 href='https://1panel.cn/docs/user_manual/settings/#3'>官方文檔</a> ",
|
||||||
domain: '加速域名',
|
domain: '加速域名',
|
||||||
|
@ -1166,6 +1166,28 @@ const message = {
|
|||||||
'请点击获取按钮,然后登录 OneDrive 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。',
|
'请点击获取按钮,然后登录 OneDrive 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。',
|
||||||
loadCode: '获取',
|
loadCode: '获取',
|
||||||
COS: '腾讯云 COS',
|
COS: '腾讯云 COS',
|
||||||
|
ap_beijing_1: '北京一区',
|
||||||
|
ap_beijing: '北京',
|
||||||
|
ap_nanjing: '南京',
|
||||||
|
ap_shanghai: '上海',
|
||||||
|
ap_guangzhou: '广州',
|
||||||
|
ap_chengdu: '成都',
|
||||||
|
ap_chongqing: '重庆',
|
||||||
|
ap_shenzhen_fsi: '深圳金融',
|
||||||
|
ap_shanghai_fsi: '上海金融',
|
||||||
|
ap_beijing_fsi: '北京金融',
|
||||||
|
ap_hongkong: '中国香港',
|
||||||
|
ap_singapore: '新加坡',
|
||||||
|
ap_mumbai: '孟买',
|
||||||
|
ap_jakarta: '雅加达',
|
||||||
|
ap_seoul: '首尔',
|
||||||
|
ap_bangkok: '曼谷',
|
||||||
|
ap_tokyo: '东京',
|
||||||
|
na_siliconvalley: '硅谷(美西)',
|
||||||
|
na_ashburn: '弗吉尼亚(美东)',
|
||||||
|
na_toronto: '多伦多',
|
||||||
|
sa_saopaulo: '圣保罗',
|
||||||
|
eu_frankfurt: '法兰克福',
|
||||||
KODO: '七牛云 Kodo',
|
KODO: '七牛云 Kodo',
|
||||||
scType: '存储类型',
|
scType: '存储类型',
|
||||||
typeStandard: '标准存储',
|
typeStandard: '标准存储',
|
||||||
@ -1177,7 +1199,6 @@ const message = {
|
|||||||
scArchive: '归档存储,适用于极低访问频率(例如半年访问1次)的业务场景。',
|
scArchive: '归档存储,适用于极低访问频率(例如半年访问1次)的业务场景。',
|
||||||
scDeep_Archive: '深度归档存储,适用于极低访问频率(例如1年访问1~2次)的业务场景。',
|
scDeep_Archive: '深度归档存储,适用于极低访问频率(例如1年访问1~2次)的业务场景。',
|
||||||
archiveHelper: '归档存储的文件无法直接下载,需要先在对应的云服务商网站进行恢复操作,请谨慎使用!',
|
archiveHelper: '归档存储的文件无法直接下载,需要先在对应的云服务商网站进行恢复操作,请谨慎使用!',
|
||||||
domainHelper: '加速域名必须包含 http:// 或者 https://',
|
|
||||||
backupAlert:
|
backupAlert:
|
||||||
"理论上只要云厂商兼容 S3 协议,就可以用现有的亚马逊 S3 云存储来备份,具体配置参考 <a target=“_blank” href='https://1panel.cn/docs/user_manual/settings/#3'>官方文档</a> ",
|
"理论上只要云厂商兼容 S3 协议,就可以用现有的亚马逊 S3 云存储来备份,具体配置参考 <a target=“_blank” href='https://1panel.cn/docs/user_manual/settings/#3'>官方文档</a> ",
|
||||||
domain: '加速域名',
|
domain: '加速域名',
|
||||||
|
@ -370,6 +370,19 @@ export function transTimeUnit(val: string): any {
|
|||||||
return val + i18n.global.t('commons.units.second');
|
return val + i18n.global.t('commons.units.second');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function splitHttp(url: string) {
|
||||||
|
if (url.indexOf('https://') != -1) {
|
||||||
|
return { proto: 'https', url: url.replaceAll('https://', '') };
|
||||||
|
}
|
||||||
|
if (url.indexOf('http://') != -1) {
|
||||||
|
return { proto: 'http', url: url.replaceAll('http://', '') };
|
||||||
|
}
|
||||||
|
return { proto: '', url: url };
|
||||||
|
}
|
||||||
|
export function spliceHttp(proto: string, url: string) {
|
||||||
|
return proto + '://' + url.replaceAll('https://', '').replaceAll('http://', '');
|
||||||
|
}
|
||||||
|
|
||||||
export function getAge(d1: string): string {
|
export function getAge(d1: string): string {
|
||||||
const dateBegin = new Date(d1);
|
const dateBegin = new Date(d1);
|
||||||
const dateEnd = new Date();
|
const dateEnd = new Date();
|
||||||
|
255
frontend/src/views/setting/backup-account/cos/index.vue
Normal file
255
frontend/src/views/setting/backup-account/cos/index.vue
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="cosData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + cosData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="cosData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
|
||||||
|
<el-input show-password clearable v-model.trim="cosData.rowData!.credential" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Region" prop="varsJson.region" :rules="Rules.requiredInput">
|
||||||
|
<el-checkbox v-model="regionInput" :label="$t('container.input')" />
|
||||||
|
<el-select
|
||||||
|
v-if="!regionInput"
|
||||||
|
v-model="cosData.rowData!.varsJson['region']"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in cities"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
<span style="float: left">{{ item.label }}</span>
|
||||||
|
<span class="option-help">
|
||||||
|
{{ item.value }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-input v-else v-model.trim="cosData.rowData!.varsJson['region']" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="cosData.rowData!.varsJson['endpointItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="endpointProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Bucket" prop="bucket">
|
||||||
|
<el-select
|
||||||
|
@change="errBuckets = false"
|
||||||
|
style="width: 80%"
|
||||||
|
v-model="cosData.rowData!.bucket"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
|
{{ $t('setting.loadBucket') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.scType')"
|
||||||
|
prop="varsJson.scType"
|
||||||
|
:rules="[Rules.requiredSelect]"
|
||||||
|
>
|
||||||
|
<el-select v-model="cosData.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="cosData.rowData!.varsJson['scType'] === 'Archive' || cosData.rowData!.varsJson['scType'] === 'Deep_Archive'"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
:closable="false"
|
||||||
|
type="warning"
|
||||||
|
:title="$t('setting.archiveHelper')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="cosData.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
|
||||||
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const buckets = ref();
|
||||||
|
const regionInput = ref();
|
||||||
|
const errBuckets = ref();
|
||||||
|
|
||||||
|
const endpointProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
const cities = [
|
||||||
|
{ value: 'ap-beijing-1', label: i18n.global.t('setting.ap_beijing_1') },
|
||||||
|
{ value: 'ap-beijing', label: i18n.global.t('setting.ap_beijing') },
|
||||||
|
{ value: 'ap-nanjing', label: i18n.global.t('setting.ap_nanjing') },
|
||||||
|
{ value: 'ap-shanghai', label: i18n.global.t('setting.ap_shanghai') },
|
||||||
|
{ value: 'ap-guangzhou', label: i18n.global.t('setting.ap_guangzhou') },
|
||||||
|
{ value: 'ap-chengdu', label: i18n.global.t('setting.ap_chengdu') },
|
||||||
|
{ value: 'ap-chongqing', label: i18n.global.t('setting.ap_chongqing') },
|
||||||
|
{ value: 'ap-shenzhen_fsi', label: i18n.global.t('setting.ap_shenzhen_fsi') },
|
||||||
|
{ value: 'ap-shanghai_fsi', label: i18n.global.t('setting.ap_shanghai_fsi') },
|
||||||
|
{ value: 'ap-beijing_fsi', label: i18n.global.t('setting.ap_beijing_fsi') },
|
||||||
|
{ value: 'ap-hongkong', label: i18n.global.t('setting.ap_hongkong') },
|
||||||
|
{ value: 'ap-singapore', label: i18n.global.t('setting.ap_singapore') },
|
||||||
|
{ value: 'ap-mumbai', label: i18n.global.t('setting.ap_mumbai') },
|
||||||
|
{ value: 'ap-jakarta', label: i18n.global.t('setting.ap_jakarta') },
|
||||||
|
{ value: 'ap-seoul', label: i18n.global.t('setting.ap_seoul') },
|
||||||
|
{ value: 'ap-bangkok', label: i18n.global.t('setting.ap_bangkok') },
|
||||||
|
{ value: 'ap-tokyo', label: i18n.global.t('setting.ap_tokyo') },
|
||||||
|
{ value: 'na-siliconvalley', label: i18n.global.t('setting.na_siliconvalley') },
|
||||||
|
{ value: 'na-ashburn', label: i18n.global.t('setting.na_ashburn') },
|
||||||
|
{ value: 'na-toronto', label: i18n.global.t('setting.na_toronto') },
|
||||||
|
{ value: 'sa-saopaulo', label: i18n.global.t('setting.sa_saopaulo') },
|
||||||
|
{ value: 'eu-frankfurt', label: i18n.global.t('setting.eu_frankfurt') },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const cosData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
buckets.value = [];
|
||||||
|
cosData.value = params;
|
||||||
|
if (params.title === 'create' || (params.title === 'edit' && !cosData.value.rowData.varsJson['scType'])) {
|
||||||
|
cosData.value.rowData.varsJson['scType'] = 'Standard';
|
||||||
|
}
|
||||||
|
if (cosData.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(cosData.value.rowData!.varsJson['endpoint']);
|
||||||
|
cosData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
|
||||||
|
endpointProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + cosData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
let item = deepCopy(cosData.value.rowData!.varsJson);
|
||||||
|
item['endpoint'] = spliceHttp(endpointProto.value, cosData.value.rowData!.varsJson['endpointItem']);
|
||||||
|
listBucket({
|
||||||
|
type: cosData.value.rowData!.type,
|
||||||
|
vars: JSON.stringify(item),
|
||||||
|
accessKey: cosData.value.rowData!.accessKey,
|
||||||
|
credential: cosData.value.rowData!.credential,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
buckets.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
buckets.value = [];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!cosData.value.rowData.bucket) {
|
||||||
|
errBuckets.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!cosData.value.rowData) return;
|
||||||
|
cosData.value.rowData!.varsJson['endpoint'] = spliceHttp(
|
||||||
|
endpointProto.value,
|
||||||
|
cosData.value.rowData!.varsJson['endpointItem'],
|
||||||
|
);
|
||||||
|
cosData.value.rowData.vars = JSON.stringify(cosData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (cosData.value.title === 'create') {
|
||||||
|
await addBackup(cosData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(cosData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.option-help {
|
||||||
|
float: right;
|
||||||
|
font-size: 12px;
|
||||||
|
word-break: break-all;
|
||||||
|
color: #8f959e;
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<LayoutContent :title="$t('commons.button.backup')">
|
<LayoutContent :title="$t('commons.button.backup')">
|
||||||
<template #main>
|
<template #main>
|
||||||
<el-form label-position="left" label-width="130px" :v-key="reflash">
|
<el-form label-width="130px" :v-key="refresh">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<div>
|
<div>
|
||||||
@ -414,8 +414,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
|
|
||||||
|
<s3Dialog ref="s3Ref" @search="search" />
|
||||||
|
<ossDialog ref="ossRef" @search="search" />
|
||||||
|
<cosDialog ref="cosRef" @search="search" />
|
||||||
|
<oneDriveDialog ref="oneDriveRef" @search="search" />
|
||||||
|
<kodoDialog ref="kodoRef" @search="search" />
|
||||||
|
<minioDialog ref="minioRef" @search="search" />
|
||||||
|
<sftpDialog ref="sftpRef" @search="search" />
|
||||||
|
<webDavDialog ref="webDavRef" @search="search" />
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
<DialogOperate ref="dialogRef" @search="search" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -423,14 +430,31 @@ import { dateFormat } from '@/utils/util';
|
|||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import OpDialog from '@/components/del-dialog/index.vue';
|
import OpDialog from '@/components/del-dialog/index.vue';
|
||||||
import { getBackupList, deleteBackup } from '@/api/modules/setting';
|
import { getBackupList, deleteBackup } from '@/api/modules/setting';
|
||||||
import DialogOperate from '@/views/setting/backup-account/operate/index.vue';
|
import s3Dialog from '@/views/setting/backup-account/s3/index.vue';
|
||||||
|
import ossDialog from '@/views/setting/backup-account/oss/index.vue';
|
||||||
|
import cosDialog from '@/views/setting/backup-account/cos/index.vue';
|
||||||
|
import oneDriveDialog from '@/views/setting/backup-account/onedrive/index.vue';
|
||||||
|
import kodoDialog from '@/views/setting/backup-account/kodo/index.vue';
|
||||||
|
import minioDialog from '@/views/setting/backup-account/minio/index.vue';
|
||||||
|
import sftpDialog from '@/views/setting/backup-account/sftp/index.vue';
|
||||||
|
import webDavDialog from '@/views/setting/backup-account/webdav/index.vue';
|
||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import { ElForm } from 'element-plus';
|
import { ElForm } from 'element-plus';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const reflash = ref(false);
|
const refresh = ref(false);
|
||||||
|
|
||||||
|
const s3Ref = ref();
|
||||||
|
const ossRef = ref();
|
||||||
|
const cosRef = ref();
|
||||||
|
const oneDriveRef = ref();
|
||||||
|
const kodoRef = ref();
|
||||||
|
const minioRef = ref();
|
||||||
|
const sftpRef = ref();
|
||||||
|
const webDavRef = ref();
|
||||||
|
|
||||||
const localData = ref<Backup.BackupInfo>({
|
const localData = ref<Backup.BackupInfo>({
|
||||||
id: 0,
|
id: 0,
|
||||||
type: 'LOCAL',
|
type: 'LOCAL',
|
||||||
@ -453,7 +477,6 @@ const ossData = ref<Backup.BackupInfo>({
|
|||||||
backupPath: '',
|
backupPath: '',
|
||||||
vars: '',
|
vars: '',
|
||||||
varsJson: {
|
varsJson: {
|
||||||
region: '',
|
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
scType: 'Standard',
|
scType: 'Standard',
|
||||||
},
|
},
|
||||||
@ -540,6 +563,7 @@ const cosData = ref<Backup.BackupInfo>({
|
|||||||
varsJson: {
|
varsJson: {
|
||||||
region: '',
|
region: '',
|
||||||
scType: 'Standard',
|
scType: 'Standard',
|
||||||
|
endpoint: '',
|
||||||
},
|
},
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
});
|
});
|
||||||
@ -609,7 +633,6 @@ const onDelete = async (row: Backup.BackupInfo) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogRef = ref();
|
|
||||||
const onOpenDialog = async (
|
const onOpenDialog = async (
|
||||||
title: string,
|
title: string,
|
||||||
accountType: string,
|
accountType: string,
|
||||||
@ -623,7 +646,32 @@ const onOpenDialog = async (
|
|||||||
title,
|
title,
|
||||||
rowData: { ...rowData },
|
rowData: { ...rowData },
|
||||||
};
|
};
|
||||||
dialogRef.value!.acceptParams(params);
|
switch (accountType) {
|
||||||
|
case 'S3':
|
||||||
|
s3Ref.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'OSS':
|
||||||
|
ossRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'COS':
|
||||||
|
cosRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'OneDrive':
|
||||||
|
oneDriveRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'KODO':
|
||||||
|
kodoRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'MINIO':
|
||||||
|
minioRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'SFTP':
|
||||||
|
sftpRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
case 'WebDAV':
|
||||||
|
webDavRef.value.acceptParams(params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
180
frontend/src/views/setting/backup-account/kodo/index.vue
Normal file
180
frontend/src/views/setting/backup-account/kodo/index.vue
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="kodoData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + kodoData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="kodoData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
|
||||||
|
<el-input show-password clearable v-model.trim="kodoData.rowData!.credential" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.domain')"
|
||||||
|
prop="varsJson.domainItem"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input v-model="kodoData.rowData!.varsJson['domainItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="domainProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Bucket" prop="bucket">
|
||||||
|
<el-select
|
||||||
|
@change="errBuckets = false"
|
||||||
|
style="width: 80%"
|
||||||
|
v-model="kodoData.rowData!.bucket"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
|
{{ $t('setting.loadBucket') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="kodoData.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
|
||||||
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const buckets = ref();
|
||||||
|
const errBuckets = ref();
|
||||||
|
|
||||||
|
const domainProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const kodoData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
buckets.value = [];
|
||||||
|
kodoData.value = params;
|
||||||
|
if (kodoData.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(kodoData.value.rowData!.varsJson['domain']);
|
||||||
|
kodoData.value.rowData!.varsJson['domainItem'] = httpItem.url;
|
||||||
|
domainProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + kodoData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
let item = deepCopy(kodoData.value.rowData!.varsJson);
|
||||||
|
item['domain'] = spliceHttp(domainProto.value, kodoData.value.rowData!.varsJson['domainItem']);
|
||||||
|
listBucket({
|
||||||
|
type: kodoData.value.rowData!.type,
|
||||||
|
vars: JSON.stringify(item),
|
||||||
|
accessKey: kodoData.value.rowData!.accessKey,
|
||||||
|
credential: kodoData.value.rowData!.credential,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
buckets.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
buckets.value = [];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!kodoData.value.rowData.bucket) {
|
||||||
|
errBuckets.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!kodoData.value.rowData) return;
|
||||||
|
kodoData.value.rowData!.varsJson['domain'] = spliceHttp(
|
||||||
|
domainProto.value,
|
||||||
|
kodoData.value.rowData!.varsJson['domainItem'],
|
||||||
|
);
|
||||||
|
kodoData.value.rowData.vars = JSON.stringify(kodoData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (kodoData.value.title === 'create') {
|
||||||
|
await addBackup(kodoData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(kodoData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
178
frontend/src/views/setting/backup-account/minio/index.vue
Normal file
178
frontend/src/views/setting/backup-account/minio/index.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="minioData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + minioData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="minioData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
|
||||||
|
<el-input show-password clearable v-model.trim="minioData.rowData!.credential" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model="minioData.rowData!.varsJson['endpointItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="endpointProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Bucket" prop="bucket">
|
||||||
|
<el-select
|
||||||
|
style="width: 80%"
|
||||||
|
@change="errBuckets = false"
|
||||||
|
v-model="minioData.rowData!.bucket"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
|
{{ $t('setting.loadBucket') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="minioData.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
|
||||||
|
import { deepCopy, splitHttp, spliceHttp } from '@/utils/util';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const buckets = ref();
|
||||||
|
const errBuckets = ref();
|
||||||
|
|
||||||
|
const endpointProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const minioData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
buckets.value = [];
|
||||||
|
minioData.value = params;
|
||||||
|
if (minioData.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(minioData.value.rowData!.varsJson['endpoint']);
|
||||||
|
minioData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
|
||||||
|
endpointProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + minioData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
let item = deepCopy(minioData.value.rowData!.varsJson);
|
||||||
|
item['endpoint'] = spliceHttp(endpointProto.value, minioData.value.rowData!.varsJson['endpointItem']);
|
||||||
|
item['endpointItem'] = undefined;
|
||||||
|
listBucket({
|
||||||
|
type: minioData.value.rowData!.type,
|
||||||
|
vars: JSON.stringify(item),
|
||||||
|
accessKey: minioData.value.rowData!.accessKey,
|
||||||
|
credential: minioData.value.rowData!.credential,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
buckets.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
buckets.value = [];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!minioData.value.rowData.bucket) {
|
||||||
|
errBuckets.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!minioData.value.rowData) return;
|
||||||
|
minioData.value.rowData!.varsJson['endpoint'] = spliceHttp(
|
||||||
|
endpointProto.value,
|
||||||
|
minioData.value.rowData!.varsJson['endpointItem'],
|
||||||
|
);
|
||||||
|
minioData.value.rowData!.varsJson['endpointItem'] = undefined;
|
||||||
|
minioData.value.rowData.vars = JSON.stringify(minioData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (minioData.value.title === 'create') {
|
||||||
|
await addBackup(minioData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(minioData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
175
frontend/src/views/setting/backup-account/onedrive/index.vue
Normal file
175
frontend/src/views/setting/backup-account/onedrive/index.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="loading"
|
||||||
|
label-position="top"
|
||||||
|
:model="onedriveData.rowData"
|
||||||
|
>
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + onedriveData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-checkbox
|
||||||
|
disabled
|
||||||
|
v-model="onedriveData.rowData!.varsJson['isCN']"
|
||||||
|
:label="$t('setting.isCN')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.code')" prop="varsJson.code" :rules="rules.driveCode">
|
||||||
|
<div style="width: 100%">
|
||||||
|
<el-input
|
||||||
|
style="width: calc(100% - 80px)"
|
||||||
|
:autosize="{ minRows: 3, maxRows: 15 }"
|
||||||
|
type="textarea"
|
||||||
|
clearable
|
||||||
|
v-model.trim="onedriveData.rowData!.varsJson['code']"
|
||||||
|
/>
|
||||||
|
<el-button class="append-button" @click="jumpAzure">
|
||||||
|
{{ $t('setting.loadCode') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ $t('setting.codeHelper') }}
|
||||||
|
<el-link
|
||||||
|
style="font-size: 12px; margin-left: 5px"
|
||||||
|
icon="Position"
|
||||||
|
@click="toDoc()"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{ $t('firewall.quickJump') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="onedriveData.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, getOneDriveInfo } from '@/api/modules/setting';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
driveCode: [{ validator: checkDriveCode, required: true, trigger: 'blur' }],
|
||||||
|
});
|
||||||
|
function checkDriveCode(rule: any, value: any, callback: any) {
|
||||||
|
const reg = /^[A-Za-z0-9_.-]+$/;
|
||||||
|
if (!reg.test(value)) {
|
||||||
|
return callback(new Error(i18n.global.t('setting.codeWarning')));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const onedriveData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
onedriveData.value = params;
|
||||||
|
title.value = i18n.global.t('commons.button.' + onedriveData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
const jumpAzure = async () => {
|
||||||
|
const res = await getOneDriveInfo();
|
||||||
|
let commonUrl = `response_type=code&client_id=${res.data}&redirect_uri=http://localhost/login/authorized&scope=offline_access+Files.ReadWrite.All+User.Read`;
|
||||||
|
if (!onedriveData.value.rowData!.varsJson['isCN']) {
|
||||||
|
window.open('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
||||||
|
} else {
|
||||||
|
window.open('https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toDoc = () => {
|
||||||
|
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!onedriveData.value.rowData) return;
|
||||||
|
onedriveData.value.rowData.vars = JSON.stringify(onedriveData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (onedriveData.value.title === 'create') {
|
||||||
|
await addBackup(onedriveData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(onedriveData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.append-button {
|
||||||
|
width: 80px;
|
||||||
|
background-color: var(--el-fill-color-light);
|
||||||
|
color: var(--el-color-info);
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,460 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-loading="loading">
|
|
||||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
|
||||||
<template #header>
|
|
||||||
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
|
||||||
</template>
|
|
||||||
<el-form
|
|
||||||
@submit.prevent
|
|
||||||
ref="formRef"
|
|
||||||
v-loading="loading"
|
|
||||||
label-position="top"
|
|
||||||
:model="dialogData.rowData"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<el-row type="flex" justify="center">
|
|
||||||
<el-col :span="22">
|
|
||||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
|
||||||
<el-tag>{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'LOCAL'"
|
|
||||||
:label="$t('setting.currentPath')"
|
|
||||||
prop="varsJson['dir']"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model="dialogData.rowData!.varsJson['dir']">
|
|
||||||
<template #prepend>
|
|
||||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="hasBucket(dialogData.rowData!.type)"
|
|
||||||
label="Access Key ID"
|
|
||||||
prop="accessKey"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.accessKey" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="hasBucket(dialogData.rowData!.type)"
|
|
||||||
label="Secret Key"
|
|
||||||
prop="credential"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="dialogData.rowData!.type === 'OneDrive'">
|
|
||||||
<el-checkbox
|
|
||||||
disabled
|
|
||||||
v-model="dialogData.rowData!.varsJson['isCN']"
|
|
||||||
:label="$t('setting.isCN')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'OneDrive'"
|
|
||||||
:label="$t('setting.code')"
|
|
||||||
prop="varsJson.code"
|
|
||||||
:rules="rules.driveCode"
|
|
||||||
>
|
|
||||||
<div style="width: 100%">
|
|
||||||
<el-input
|
|
||||||
style="width: calc(100% - 80px)"
|
|
||||||
:autosize="{ minRows: 3, maxRows: 15 }"
|
|
||||||
type="textarea"
|
|
||||||
clearable
|
|
||||||
v-model.trim="dialogData.rowData!.varsJson['code']"
|
|
||||||
/>
|
|
||||||
<el-button class="append-button" @click="jumpAzure">
|
|
||||||
{{ $t('setting.loadCode') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<span class="input-help">
|
|
||||||
{{ $t('setting.codeHelper') }}
|
|
||||||
<el-link
|
|
||||||
style="font-size: 12px; margin-left: 5px"
|
|
||||||
icon="Position"
|
|
||||||
@click="toDoc()"
|
|
||||||
type="primary"
|
|
||||||
>
|
|
||||||
{{ $t('firewall.quickJump') }}
|
|
||||||
</el-link>
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'S3' || dialogData.rowData!.type === 'COS'"
|
|
||||||
label="Region"
|
|
||||||
prop="varsJson.region"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['region']" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="hasEndpoint(dialogData.rowData!.type)"
|
|
||||||
label="Endpoint"
|
|
||||||
prop="varsJson.endpoint"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['endpoint']" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'KODO'"
|
|
||||||
:label="$t('setting.domain')"
|
|
||||||
prop="varsJson.domain"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['domain']" />
|
|
||||||
<span class="input-help">{{ $t('setting.domainHelper') }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'MINIO'"
|
|
||||||
label="Endpoint"
|
|
||||||
prop="varsJson.endpointItem"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model="dialogData.rowData!.varsJson['endpointItem']">
|
|
||||||
<template #prepend>
|
|
||||||
<el-select v-model.trim="endpoints" style="width: 80px">
|
|
||||||
<el-option label="http" value="http" />
|
|
||||||
<el-option label="https" value="https" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type !== '' && hasBucket(dialogData.rowData!.type)"
|
|
||||||
label="Bucket"
|
|
||||||
prop="bucket"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
style="width: 80%"
|
|
||||||
@change="errBuckets = false"
|
|
||||||
v-model="dialogData.rowData!.bucket"
|
|
||||||
>
|
|
||||||
<el-option v-for="item in buckets" :key="item" :value="item" />
|
|
||||||
</el-select>
|
|
||||||
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
|
||||||
{{ $t('setting.loadBucket') }}
|
|
||||||
</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="isSftpOrWebDAV(dialogData.rowData!.type)">
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'SFTP'"
|
|
||||||
:label="$t('setting.address')"
|
|
||||||
prop="varsJson.address"
|
|
||||||
:rules="Rules.host"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type === 'WebDAV'"
|
|
||||||
:label="$t('setting.address')"
|
|
||||||
prop="varsJson.address"
|
|
||||||
:rules="Rules.requiredInput"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
|
|
||||||
<span class="input-help">http://172.16.10.111</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
|
|
||||||
<el-input-number
|
|
||||||
:min="0"
|
|
||||||
:max="65535"
|
|
||||||
v-model.number="dialogData.rowData!.varsJson['port']"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('commons.login.username')"
|
|
||||||
prop="accessKey"
|
|
||||||
:rules="[Rules.requiredInput]"
|
|
||||||
>
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.accessKey" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('commons.login.password')"
|
|
||||||
prop="credential"
|
|
||||||
:rules="[Rules.requiredInput]"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
type="password"
|
|
||||||
clearable
|
|
||||||
show-password
|
|
||||||
v-model.trim="dialogData.rowData!.credential"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
|
|
||||||
<el-input v-model.trim="dialogData.rowData!.bucket" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
<el-form-item
|
|
||||||
v-if="dialogData.rowData!.type !== 'LOCAL' && !isSftpOrWebDAV(dialogData.rowData!.type)"
|
|
||||||
:label="$t('setting.backupDir')"
|
|
||||||
prop="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="handleClose">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-drawer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import { Rules } from '@/global/form-rules';
|
|
||||||
import FileList from '@/components/file-list/index.vue';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { ElForm } from 'element-plus';
|
|
||||||
import { Backup } from '@/api/interface/backup';
|
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
|
||||||
import { addBackup, editBackup, getOneDriveInfo, listBucket } from '@/api/modules/setting';
|
|
||||||
import { deepCopy } from '@/utils/util';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
|
||||||
const formRef = ref<FormInstance>();
|
|
||||||
const buckets = ref();
|
|
||||||
const errBuckets = ref();
|
|
||||||
|
|
||||||
const endpoints = ref('http');
|
|
||||||
const rules = reactive({
|
|
||||||
driveCode: [{ validator: checkDriveCode, required: true, trigger: 'blur' }],
|
|
||||||
});
|
|
||||||
function checkDriveCode(rule: any, value: any, callback: any) {
|
|
||||||
const reg = /^[A-Za-z0-9_.-]+$/;
|
|
||||||
if (!reg.test(value)) {
|
|
||||||
return callback(new Error(i18n.global.t('setting.codeWarning')));
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
title: string;
|
|
||||||
rowData?: Backup.BackupInfo;
|
|
||||||
getTableList?: () => Promise<any>;
|
|
||||||
}
|
|
||||||
const title = ref<string>('');
|
|
||||||
const drawerVisible = ref(false);
|
|
||||||
const dialogData = ref<DialogProps>({
|
|
||||||
title: '',
|
|
||||||
});
|
|
||||||
const acceptParams = (params: DialogProps): void => {
|
|
||||||
buckets.value = [];
|
|
||||||
dialogData.value = params;
|
|
||||||
if (dialogData.value.title === 'edit' && dialogData.value.rowData!.type === 'MINIO') {
|
|
||||||
if (dialogData.value.rowData!.varsJson['endpoint'].indexOf('://') !== 0) {
|
|
||||||
endpoints.value = dialogData.value.rowData!.varsJson['endpoint'].split('://')[0];
|
|
||||||
dialogData.value.rowData!.varsJson['endpointItem'] =
|
|
||||||
dialogData.value.rowData!.varsJson['endpoint'].split('://')[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
drawerVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
emit('search');
|
|
||||||
drawerVisible.value = false;
|
|
||||||
};
|
|
||||||
const jumpAzure = async () => {
|
|
||||||
const res = await getOneDriveInfo();
|
|
||||||
let commonUrl = `response_type=code&client_id=${res.data}&redirect_uri=http://localhost/login/authorized&scope=offline_access+Files.ReadWrite.All+User.Read`;
|
|
||||||
if (!dialogData.value.rowData!.varsJson['isCN']) {
|
|
||||||
window.open('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
|
||||||
} else {
|
|
||||||
window.open('https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadDir = async (path: string) => {
|
|
||||||
dialogData.value.rowData!.varsJson['dir'] = path;
|
|
||||||
};
|
|
||||||
function hasBucket(val: string) {
|
|
||||||
return val === 'OSS' || val === 'S3' || val === 'MINIO' || val === 'COS' || val === 'KODO';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasEndpoint(val: string) {
|
|
||||||
return val === 'OSS' || val === 'S3';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSftpOrWebDAV(val: string) {
|
|
||||||
return val === 'SFTP' || val === 'WebDAV';
|
|
||||||
}
|
|
||||||
|
|
||||||
const toDoc = () => {
|
|
||||||
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBuckets = async (formEl: FormInstance | undefined) => {
|
|
||||||
if (!formEl) return;
|
|
||||||
formEl.validate(async (valid) => {
|
|
||||||
if (!valid) return;
|
|
||||||
loading.value = true;
|
|
||||||
let item = deepCopy(dialogData.value.rowData!.varsJson);
|
|
||||||
if (dialogData.value.rowData!.type === 'MINIO') {
|
|
||||||
dialogData.value.rowData!.varsJson['endpointItem'] = dialogData.value
|
|
||||||
.rowData!.varsJson['endpointItem'].replace('https://', '')
|
|
||||||
.replace('http://', '');
|
|
||||||
item['endpoint'] = endpoints.value + '://' + dialogData.value.rowData!.varsJson['endpointItem'];
|
|
||||||
item['endpointItem'] = undefined;
|
|
||||||
}
|
|
||||||
listBucket({
|
|
||||||
type: dialogData.value.rowData!.type,
|
|
||||||
vars: JSON.stringify(item),
|
|
||||||
accessKey: dialogData.value.rowData!.accessKey,
|
|
||||||
credential: dialogData.value.rowData!.credential,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
buckets.value = res.data;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
buckets.value = [];
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
|
||||||
if (hasBucket(dialogData.value.rowData.type) && !dialogData.value.rowData.bucket) {
|
|
||||||
errBuckets.value = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!formEl) return;
|
|
||||||
formEl.validate(async (valid) => {
|
|
||||||
if (!valid) return;
|
|
||||||
if (!dialogData.value.rowData) return;
|
|
||||||
if (dialogData.value.rowData!.type === 'MINIO') {
|
|
||||||
dialogData.value.rowData!.varsJson['endpointItem'].replace('https://', '').replace('http://', '');
|
|
||||||
dialogData.value.rowData!.varsJson['endpoint'] =
|
|
||||||
endpoints.value + '://' + dialogData.value.rowData!.varsJson['endpointItem'];
|
|
||||||
dialogData.value.rowData!.varsJson['endpointItem'] = undefined;
|
|
||||||
}
|
|
||||||
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
|
|
||||||
loading.value = true;
|
|
||||||
if (dialogData.value.title === 'create') {
|
|
||||||
await addBackup(dialogData.value.rowData)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
emit('search');
|
|
||||||
drawerVisible.value = false;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await editBackup(dialogData.value.rowData)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
emit('search');
|
|
||||||
drawerVisible.value = false;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.append-button {
|
|
||||||
width: 80px;
|
|
||||||
background-color: var(--el-fill-color-light);
|
|
||||||
color: var(--el-color-info);
|
|
||||||
}
|
|
||||||
</style>
|
|
198
frontend/src/views/setting/backup-account/oss/index.vue
Normal file
198
frontend/src/views/setting/backup-account/oss/index.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="ossData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + ossData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="ossData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
|
||||||
|
<el-input show-password clearable v-model.trim="ossData.rowData!.credential" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model="ossData.rowData!.varsJson['endpointItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="endpointProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Bucket" prop="bucket">
|
||||||
|
<el-select
|
||||||
|
@change="errBuckets = false"
|
||||||
|
style="width: 80%"
|
||||||
|
v-model="ossData.rowData!.bucket"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
|
{{ $t('setting.loadBucket') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.scType')"
|
||||||
|
prop="varsJson.scType"
|
||||||
|
:rules="[Rules.requiredSelect]"
|
||||||
|
>
|
||||||
|
<el-select v-model="ossData.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="ossData.rowData!.varsJson['scType'] === 'Archive' || ossData.rowData!.varsJson['scType'] === 'ColdArchive'"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
:closable="false"
|
||||||
|
type="warning"
|
||||||
|
:title="$t('setting.archiveHelper')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="ossData.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
|
||||||
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const buckets = ref();
|
||||||
|
const errBuckets = ref();
|
||||||
|
|
||||||
|
const endpointProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const ossData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
buckets.value = [];
|
||||||
|
ossData.value = params;
|
||||||
|
if (params.title === 'create' || (params.title === 'edit' && !ossData.value.rowData.varsJson['scType'])) {
|
||||||
|
ossData.value.rowData.varsJson['scType'] = 'Standard';
|
||||||
|
}
|
||||||
|
if (ossData.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(ossData.value.rowData!.varsJson['endpoint']);
|
||||||
|
ossData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
|
||||||
|
endpointProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + ossData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
let item = deepCopy(ossData.value.rowData!.varsJson);
|
||||||
|
item['endpoint'] = spliceHttp(endpointProto.value, ossData.value.rowData!.varsJson['endpointItem']);
|
||||||
|
listBucket({
|
||||||
|
type: ossData.value.rowData!.type,
|
||||||
|
vars: JSON.stringify(item),
|
||||||
|
accessKey: ossData.value.rowData!.accessKey,
|
||||||
|
credential: ossData.value.rowData!.credential,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
buckets.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
buckets.value = [];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!ossData.value.rowData.bucket) {
|
||||||
|
errBuckets.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!ossData.value.rowData) return;
|
||||||
|
ossData.value.rowData!.varsJson['endpoint'] = spliceHttp(
|
||||||
|
endpointProto.value,
|
||||||
|
ossData.value.rowData!.varsJson['endpointItem'],
|
||||||
|
);
|
||||||
|
ossData.value.rowData.vars = JSON.stringify(ossData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (ossData.value.title === 'create') {
|
||||||
|
await addBackup(ossData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(ossData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
197
frontend/src/views/setting/backup-account/s3/index.vue
Normal file
197
frontend/src/views/setting/backup-account/s3/index.vue
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="s3Data.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + s3Data.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model.trim="s3Data.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
|
||||||
|
<el-input show-password clearable v-model.trim="s3Data.rowData!.credential" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Region" prop="varsJson.region" :rules="Rules.requiredInput">
|
||||||
|
<el-input pro v-model.trim="s3Data.rowData!.varsJson['region']" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model="s3Data.rowData!.varsJson['endpointItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="endpointProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Bucket" prop="bucket">
|
||||||
|
<el-select @change="errBuckets = false" style="width: 80%" v-model="s3Data.rowData!.bucket">
|
||||||
|
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
|
{{ $t('setting.loadBucket') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.scType')"
|
||||||
|
prop="varsJson.scType"
|
||||||
|
:rules="[Rules.requiredSelect]"
|
||||||
|
>
|
||||||
|
<el-select v-model="s3Data.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="s3Data.rowData!.varsJson['scType'] === 'Archive' || s3Data.rowData!.varsJson['scType'] === 'ColdArchive'"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
:closable="false"
|
||||||
|
type="warning"
|
||||||
|
:title="$t('setting.archiveHelper')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
|
||||||
|
<el-input clearable v-model.trim="s3Data.rowData!.backupPath" placeholder="/1panel" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
|
||||||
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const buckets = ref();
|
||||||
|
const errBuckets = ref();
|
||||||
|
|
||||||
|
const endpointProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const s3Data = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
buckets.value = [];
|
||||||
|
s3Data.value = params;
|
||||||
|
if (params.title === 'create' || (params.title === 'edit' && !s3Data.value.rowData.varsJson['scType'])) {
|
||||||
|
s3Data.value.rowData.varsJson['scType'] = 'STANDARD';
|
||||||
|
}
|
||||||
|
if (s3Data.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(s3Data.value.rowData!.varsJson['endpoint']);
|
||||||
|
s3Data.value.rowData!.varsJson['endpointItem'] = httpItem.url;
|
||||||
|
endpointProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + s3Data.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
loading.value = true;
|
||||||
|
let item = deepCopy(s3Data.value.rowData!.varsJson);
|
||||||
|
item['endpoint'] = spliceHttp(endpointProto.value, s3Data.value.rowData!.varsJson['endpointItem']);
|
||||||
|
listBucket({
|
||||||
|
type: s3Data.value.rowData!.type,
|
||||||
|
vars: JSON.stringify(item),
|
||||||
|
accessKey: s3Data.value.rowData!.accessKey,
|
||||||
|
credential: s3Data.value.rowData!.credential,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
buckets.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
buckets.value = [];
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!s3Data.value.rowData.bucket) {
|
||||||
|
errBuckets.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!s3Data.value.rowData) return;
|
||||||
|
s3Data.value.rowData!.varsJson['endpoint'] = spliceHttp(
|
||||||
|
endpointProto.value,
|
||||||
|
s3Data.value.rowData!.varsJson['endpointItem'],
|
||||||
|
);
|
||||||
|
s3Data.value.rowData.vars = JSON.stringify(s3Data.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (s3Data.value.title === 'create') {
|
||||||
|
await addBackup(s3Data.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(s3Data.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
138
frontend/src/views/setting/backup-account/sftp/index.vue
Normal file
138
frontend/src/views/setting/backup-account/sftp/index.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="sftpData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + sftpData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.host">
|
||||||
|
<el-input v-model.trim="sftpData.rowData!.varsJson['address']" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
v-model.number="sftpData.rowData!.varsJson['port']"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('commons.login.username')"
|
||||||
|
prop="accessKey"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
|
<el-input v-model.trim="sftpData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('commons.login.password')"
|
||||||
|
prop="credential"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="password"
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model.trim="sftpData.rowData!.credential"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
|
||||||
|
<el-input v-model.trim="sftpData.rowData!.bucket" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup } from '@/api/modules/setting';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
getTableList?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const sftpData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
sftpData.value = params;
|
||||||
|
if (sftpData.value.title === 'create') {
|
||||||
|
sftpData.value.rowData.varsJson['port'] = 22;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + sftpData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!sftpData.value.rowData) return;
|
||||||
|
sftpData.value.rowData.vars = JSON.stringify(sftpData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (sftpData.value.title === 'create') {
|
||||||
|
await addBackup(sftpData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(sftpData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
156
frontend/src/views/setting/backup-account/webdav/index.vue
Normal file
156
frontend/src/views/setting/backup-account/webdav/index.vue
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="webdavData.rowData">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
|
<el-tag>{{ $t('setting.' + webdavData.rowData!.type) }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.address')"
|
||||||
|
prop="varsJson.address"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input v-model="webdavData.rowData!.varsJson['addressItem']">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model.trim="addressProto" style="width: 100px">
|
||||||
|
<el-option label="http" value="http" />
|
||||||
|
<el-option label="https" value="https" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
v-model.number="webdavData.rowData!.varsJson['port']"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('commons.login.username')"
|
||||||
|
prop="accessKey"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
|
<el-input v-model.trim="webdavData.rowData!.accessKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('commons.login.password')"
|
||||||
|
prop="credential"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="password"
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model.trim="webdavData.rowData!.credential"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
|
||||||
|
<el-input v-model.trim="webdavData.rowData!.bucket" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="handleClose">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { Backup } from '@/api/interface/backup';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { addBackup, editBackup } from '@/api/modules/setting';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { spliceHttp, splitHttp } from '@/utils/util';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const addressProto = ref('http');
|
||||||
|
const emit = defineEmits(['search']);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Backup.BackupInfo;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const webdavData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
webdavData.value = params;
|
||||||
|
if (webdavData.value.title === 'edit') {
|
||||||
|
let httpItem = splitHttp(webdavData.value.rowData!.varsJson['address']);
|
||||||
|
webdavData.value.rowData!.varsJson['addressItem'] = httpItem.url;
|
||||||
|
addressProto.value = httpItem.proto;
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('commons.button.' + webdavData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!webdavData.value.rowData) return;
|
||||||
|
webdavData.value.rowData!.varsJson['address'] = spliceHttp(
|
||||||
|
addressProto.value,
|
||||||
|
webdavData.value.rowData!.varsJson['addressItem'],
|
||||||
|
);
|
||||||
|
webdavData.value.rowData.vars = JSON.stringify(webdavData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
|
if (webdavData.value.title === 'create') {
|
||||||
|
await addBackup(webdavData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editBackup(webdavData.value.rowData)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user