mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
fix: 优化导入备份限制 (#361)
This commit is contained in:
parent
67bb30c10c
commit
fa983bdcc9
@ -519,6 +519,12 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||||
|
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -547,7 +553,6 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
|
|||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /files/chunkupload [post]
|
// @Router /files/chunkupload [post]
|
||||||
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"上传文件 [path]","formatEN":"Upload file [path]"}
|
|
||||||
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||||
fileForm, err := c.FormFile("chunk")
|
fileForm, err := c.FormFile("chunk")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,12 @@
|
|||||||
<em>{{ $t('database.clickHelper') }}</em>
|
<em>{{ $t('database.clickHelper') }}</em>
|
||||||
</div>
|
</div>
|
||||||
<template #tip>
|
<template #tip>
|
||||||
|
<el-progress
|
||||||
|
v-if="isUpload"
|
||||||
|
text-inside
|
||||||
|
:stroke-width="12"
|
||||||
|
:percentage="uploadPrecent"
|
||||||
|
></el-progress>
|
||||||
<div v-if="type === 'mysql'" class="el-upload__tip">
|
<div v-if="type === 'mysql'" class="el-upload__tip">
|
||||||
<span class="input-help">{{ $t('database.supportUpType') }}</span>
|
<span class="input-help">{{ $t('database.supportUpType') }}</span>
|
||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
@ -26,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<el-button v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
|
<el-button :disabled="isUpload" v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
|
||||||
{{ $t('commons.button.upload') }}
|
{{ $t('commons.button.upload') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
@ -82,11 +88,13 @@ import i18n from '@/lang';
|
|||||||
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
||||||
import { File } from '@/api/interface/file';
|
import { File } from '@/api/interface/file';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { BatchDeleteFile, CheckFile, GetUploadList, UploadFileData } from '@/api/modules/files';
|
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
|
||||||
import { loadBaseDir } from '@/api/modules/setting';
|
import { loadBaseDir } from '@/api/modules/setting';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
|
const isUpload = ref();
|
||||||
|
const uploadPrecent = ref<number>(0);
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const baseDir = ref();
|
const baseDir = ref();
|
||||||
|
|
||||||
@ -166,20 +174,12 @@ const beforeAvatarUpload = (rawFile) => {
|
|||||||
MsgError(i18n.global.t('commons.msg.unSupportType'));
|
MsgError(i18n.global.t('commons.msg.unSupportType'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rawFile.size / 1024 / 1024 > 50) {
|
|
||||||
MsgError(i18n.global.t('commons.msg.unSupportSize', [50]));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!rawFile.name.endsWith('.sql') && !rawFile.name.endsWith('.tar.gz') && !rawFile.name.endsWith('.sql.gz')) {
|
if (!rawFile.name.endsWith('.sql') && !rawFile.name.endsWith('.tar.gz') && !rawFile.name.endsWith('.sql.gz')) {
|
||||||
MsgError(i18n.global.t('commons.msg.unSupportType'));
|
MsgError(i18n.global.t('commons.msg.unSupportType'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rawFile.size / 1024 / 1024 > 10) {
|
|
||||||
MsgError(i18n.global.t('commons.msg.unSupportSize', [10]));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,42 +194,72 @@ const handleClose = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const formData = new FormData();
|
|
||||||
if (uploaderFiles.value.length !== 1) {
|
if (uploaderFiles.value.length !== 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!uploaderFiles.value[0]!.raw.name) {
|
const file = uploaderFiles.value[0];
|
||||||
|
if (!file.raw.name) {
|
||||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,256}$/;
|
let reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,256}$/;
|
||||||
if (!reg.test(uploaderFiles.value[0]!.raw.name)) {
|
if (!reg.test(file.raw.name)) {
|
||||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await CheckFile(baseDir.value + uploaderFiles.value[0]!.raw.name);
|
const res = await CheckFile(baseDir.value + file.raw.name);
|
||||||
if (!res.data) {
|
if (!res.data) {
|
||||||
MsgError(i18n.global.t('commons.msg.fileExist'));
|
MsgError(i18n.global.t('commons.msg.fileExist'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formData.append('file', uploaderFiles.value[0]!.raw);
|
let isOk = beforeAvatarUpload(file.raw);
|
||||||
let isOk = beforeAvatarUpload(uploaderFiles.value[0]!.raw);
|
|
||||||
if (!isOk) {
|
if (!isOk) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formData.append('path', baseDir.value);
|
submitUpload(file);
|
||||||
loading.value = true;
|
};
|
||||||
UploadFileData(formData, {})
|
|
||||||
.then(() => {
|
const submitUpload = async (file: any) => {
|
||||||
loading.value = false;
|
isUpload.value = true;
|
||||||
|
const CHUNK_SIZE = 1024 * 1024;
|
||||||
|
const fileSize = file.size;
|
||||||
|
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
|
||||||
|
let uploadedChunkCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < chunkCount; i++) {
|
||||||
|
const start = i * CHUNK_SIZE;
|
||||||
|
const end = Math.min(start + CHUNK_SIZE, fileSize);
|
||||||
|
const chunk = file.raw.slice(start, end);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('filename', file.name);
|
||||||
|
formData.append('path', baseDir.value);
|
||||||
|
formData.append('chunk', chunk);
|
||||||
|
formData.append('chunkIndex', i.toString());
|
||||||
|
formData.append('chunkCount', chunkCount.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ChunkUploadFileData(formData, {
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
const progress = Math.round(
|
||||||
|
((uploadedChunkCount + progressEvent.loaded / progressEvent.total) * 100) / chunkCount,
|
||||||
|
);
|
||||||
|
uploadPrecent.value = progress;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
uploadedChunkCount++;
|
||||||
|
} catch (error) {
|
||||||
|
isUpload.value = false;
|
||||||
|
}
|
||||||
|
if (uploadedChunkCount == chunkCount) {
|
||||||
|
isUpload.value = false;
|
||||||
uploadRef.value?.clearFiles();
|
uploadRef.value?.clearFiles();
|
||||||
uploaderFiles.value = [];
|
uploaderFiles.value = [];
|
||||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||||
search();
|
search();
|
||||||
})
|
}
|
||||||
.catch(() => {
|
}
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBatchDelete = async (row: File.File | null) => {
|
const onBatchDelete = async (row: File.File | null) => {
|
||||||
|
@ -305,7 +305,7 @@ const message = {
|
|||||||
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
|
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
|
||||||
|
|
||||||
selectFile: 'Select file',
|
selectFile: 'Select file',
|
||||||
supportUpType: 'Only sql, sql.gz, and tar.gz files within 10 MB are supported',
|
supportUpType: 'Only sql, sql.gz, and tar.gz files are supported',
|
||||||
zipFormat: 'tar.gz compressed package structure: test.tar.gz compressed package must contain test.sql',
|
zipFormat: 'tar.gz compressed package structure: test.tar.gz compressed package must contain test.sql',
|
||||||
|
|
||||||
currentStatus: 'Current state',
|
currentStatus: 'Current state',
|
||||||
@ -965,7 +965,7 @@ const message = {
|
|||||||
type: 'Type',
|
type: 'Type',
|
||||||
static: 'Static',
|
static: 'Static',
|
||||||
deployment: 'Deployment',
|
deployment: 'Deployment',
|
||||||
supportUpType: 'Only .tar.gz files within 50 MB are supported',
|
supportUpType: 'Only .tar.gz files are supported',
|
||||||
zipFormat: '.tar.gz compressed package structure: test.tar.gz compressed package must contain {0} file',
|
zipFormat: '.tar.gz compressed package structure: test.tar.gz compressed package must contain {0} file',
|
||||||
proxy: 'Reverse Proxy',
|
proxy: 'Reverse Proxy',
|
||||||
alias: 'Path Name',
|
alias: 'Path Name',
|
||||||
|
@ -311,7 +311,7 @@ const message = {
|
|||||||
selectFile: '选择文件',
|
selectFile: '选择文件',
|
||||||
dropHelper: '将上传文件拖拽到此处,或者',
|
dropHelper: '将上传文件拖拽到此处,或者',
|
||||||
clickHelper: '点击上传',
|
clickHelper: '点击上传',
|
||||||
supportUpType: '仅支持 10M 以内 sql、sql.gz、tar.gz 文件',
|
supportUpType: '仅支持 sql、sql.gz、tar.gz 文件',
|
||||||
zipFormat: 'tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 test.sql',
|
zipFormat: 'tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 test.sql',
|
||||||
|
|
||||||
currentStatus: '当前状态',
|
currentStatus: '当前状态',
|
||||||
@ -971,7 +971,7 @@ const message = {
|
|||||||
type: '类型',
|
type: '类型',
|
||||||
static: '静态网站',
|
static: '静态网站',
|
||||||
deployment: '一键部署',
|
deployment: '一键部署',
|
||||||
supportUpType: '仅支持 50M 以内 .tar.gz 文件',
|
supportUpType: '仅支持 .tar.gz 文件',
|
||||||
zipFormat: '.tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 {0} 文件',
|
zipFormat: '.tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 {0} 文件',
|
||||||
proxy: '反向代理',
|
proxy: '反向代理',
|
||||||
alias: '代号',
|
alias: '代号',
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
ref="uploadRef"
|
ref="uploadRef"
|
||||||
:on-change="fileOnChange"
|
:on-change="fileOnChange"
|
||||||
v-loading="loading"
|
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:on-exceed="handleExceed"
|
:on-exceed="handleExceed"
|
||||||
>
|
>
|
||||||
@ -24,8 +23,10 @@
|
|||||||
{{ $t('database.dropHelper') }}
|
{{ $t('database.dropHelper') }}
|
||||||
<em>{{ $t('database.clickHelper') }}</em>
|
<em>{{ $t('database.clickHelper') }}</em>
|
||||||
</div>
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<el-progress v-if="loading" text-inside :stroke-width="12" :percentage="uploadPrecent"></el-progress>
|
||||||
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<el-progress v-if="loading" :text-inside="true" :stroke-width="26" :percentage="uploadPrecent"></el-progress>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user