feat: WebDAV 支持连接 Alist (#3836)

Refs #3495
This commit is contained in:
ssongliu 2024-02-05 17:28:12 +08:00 committed by GitHub
parent 9e980e8e0c
commit d8669b90bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 65 additions and 53 deletions

View file

@ -70,6 +70,8 @@ func Init() {
migrations.UpdateCronjobSpec,
migrations.UpdateBackupRecordPath,
migrations.UpdateSnapshotRecords,
migrations.UpdateWebDavConf,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -451,3 +451,29 @@ var UpdateSnapshotRecords = &gormigrate.Migration{
return nil
},
}
var UpdateWebDavConf = &gormigrate.Migration{
ID: "20240205-update-webdav-conf",
Migrate: func(tx *gorm.DB) error {
var backup model.BackupAccount
_ = tx.Where("type = ?", constant.WebDAV).First(&backup).Error
if backup.ID == 0 {
return nil
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return err
}
delete(varMap, "addressItem")
if port, ok := varMap["port"]; ok {
varMap["address"] = fmt.Sprintf("%s:%v", varMap["address"], port)
delete(varMap, "port")
}
vars, _ := json.Marshal(varMap)
if err := tx.Model(&model.BackupAccount{}).Where("id = ?", backup.ID).Updates(map[string]interface{}{"vars": string(vars)}).Error; err != nil {
return err
}
return nil
},
}

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"os"
"path"
"strings"
"github.com/studio-b12/gowebdav"
@ -43,7 +44,7 @@ func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
}
func (s webDAVClient) Upload(src, target string) (bool, error) {
targetFilePath := s.Bucket + "/" + target
targetFilePath := path.Join(s.Bucket, target)
srcFile, err := os.Open(src)
if err != nil {
return false, err
@ -62,7 +63,7 @@ func (s webDAVClient) ListBuckets() ([]interface{}, error) {
}
func (s webDAVClient) Download(src, target string) (bool, error) {
srcPath := s.Bucket + "/" + src
srcPath := path.Join(s.Bucket, src)
info, err := s.client.Stat(srcPath)
if err != nil {
return false, err
@ -85,30 +86,30 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
return true, err
}
func (s webDAVClient) Exist(path string) (bool, error) {
if _, err := s.client.Stat(s.Bucket + "/" + path); err != nil {
func (s webDAVClient) Exist(pathItem string) (bool, error) {
if _, err := s.client.Stat(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) Size(path string) (int64, error) {
file, err := s.client.Stat(s.Bucket + "/" + path)
func (s webDAVClient) Size(pathItem string) (int64, error) {
file, err := s.client.Stat(path.Join(s.Bucket, pathItem))
if err != nil {
return 0, err
}
return file.Size(), nil
}
func (s webDAVClient) Delete(filePath string) (bool, error) {
if err := s.client.Remove(s.Bucket + "/" + filePath); err != nil {
func (s webDAVClient) Delete(pathItem string) (bool, error) {
if err := s.client.Remove(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) ListObjects(prefix string) ([]string, error) {
files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
files, err := s.client.ReadDir(path.Join(s.Bucket, prefix))
if err != nil {
return nil, err
}

View file

@ -1250,6 +1250,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV connect Alist can refer to the official documentation',
OneDrive: 'Microsoft OneDrive',
isCN: 'Century Internet',
isNotCN: 'International Version',

View file

@ -1173,6 +1173,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV 連接 Alist 可參考官方文檔',
OneDrive: '微軟 OneDrive',
isCN: '世紀互聯',
isNotCN: '國際版',
@ -1183,7 +1184,7 @@ const message = {
refreshTime: '令牌刷新時間',
refreshStatus: '令牌刷新狀態',
codeWarning: '當前授權碼格式錯誤請重新確認',
backupDir: '備份路徑',
backupDir: '備份目录',
code: '授權碼',
codeHelper:
'請點擊獲取按鈕然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容粘貼到該輸入框中具體操作可參考官方文檔',

View file

@ -1174,6 +1174,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV 连接 Alist 可参考官方文档',
OneDrive: '微软 OneDrive',
isCN: '世纪互联',
isNotCN: '国际版',
@ -1184,7 +1185,7 @@ const message = {
refreshTime: '令牌刷新时间',
refreshStatus: '令牌刷新状态',
codeWarning: '当前授权码格式错误请重新确认',
backupDir: '备份路径',
backupDir: '备份目录',
code: '授权码',
codeHelper:
'请点击获取按钮然后登录 OneDrive 复制跳转链接中 code 后面的内容粘贴到该输入框中具体操作可参考官方文档',

View file

@ -372,7 +372,7 @@
<el-form-item :label="$t('commons.table.port')">
{{ sftpData.varsJson['port'] }}
</el-form-item>
<el-form-item :label="$t('setting.path')">
<el-form-item :label="$t('setting.backupDir')">
{{ sftpData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
@ -408,10 +408,7 @@
<el-form-item :label="$t('setting.address')">
{{ webDAVData.varsJson['address'] }}
</el-form-item>
<el-form-item :label="$t('commons.table.port')">
{{ webDAVData.varsJson['port'] }}
</el-form-item>
<el-form-item :label="$t('setting.path')">
<el-form-item :label="$t('setting.backupDir')">
{{ webDAVData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">

View file

@ -39,7 +39,7 @@
v-model.trim="sftpData.rowData!.credential"
/>
</el-form-item>
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="sftpData.rowData!.bucket" />
</el-form-item>
</el-col>

View file

@ -12,20 +12,21 @@
</el-form-item>
<el-form-item
:label="$t('setting.address')"
prop="varsJson.addressItem"
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="[portRule]">
<el-input-number v-model.number="webdavData.rowData!.varsJson['port']" />
<el-input v-model="webdavData.rowData!.varsJson['address']" />
<span class="input-help">
{{ $t('setting.WebDAVAlist') }}
<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('commons.login.username')"
@ -46,7 +47,7 @@
v-model.trim="webdavData.rowData!.credential"
/>
</el-form-item>
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="webdavData.rowData!.bucket" />
</el-form-item>
</el-col>
@ -67,7 +68,7 @@
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
@ -75,13 +76,11 @@ 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 { checkPort, 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 {
@ -94,24 +93,8 @@ const webdavData = ref<DialogProps>({
title: '',
});
const portRule = reactive({ validator: checkportRule, trigger: 'blur', type: 'number' });
function checkportRule(rule: any, value: any, callback: any) {
if (value !== undefined && value !== null) {
if (checkPort(value)) {
return callback(new Error(i18n.global.t('commons.rule.port')));
}
}
callback();
}
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;
};
@ -121,15 +104,15 @@ const handleClose = () => {
drawerVisible.value = false;
};
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 (!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') {