diff --git a/daemon/src/routers/file_router.ts b/daemon/src/routers/file_router.ts index 8bb43566..b0cc7264 100755 --- a/daemon/src/routers/file_router.ts +++ b/daemon/src/routers/file_router.ts @@ -203,3 +203,18 @@ routerApp.on("file/compress", async (ctx, data) => { protocol.responseError(ctx, error); } }); + +// Check file is exists in path +routerApp.on("file/exists", async (ctx, data) => { + try { + const fileName = data.uploadFilename; + const dir = data.uploadDir; + const fileManager = getFileManager(data.instanceUuid); + + const exists = fileManager.exists(dir, fileName); + protocol.response(ctx, exists); + } catch (error: any) { + console.log(error); + protocol.responseError(ctx, error); + } +}); diff --git a/daemon/src/routers/http_router.ts b/daemon/src/routers/http_router.ts index eadc94b7..82423687 100755 --- a/daemon/src/routers/http_router.ts +++ b/daemon/src/routers/http_router.ts @@ -81,7 +81,9 @@ router.post("/upload/:key", async (ctx) => { if (!fileManager.checkPath(fileSaveRelativePath)) throw new Error("Access denied: Invalid destination"); const fileSaveAbsolutePath = fileManager.toAbsolutePath(fileSaveRelativePath); - await fs.move(uploadedFile.filepath, fileSaveAbsolutePath); + await fs.move(uploadedFile.filepath, fileSaveAbsolutePath, { + overwrite: ctx.query.overwrite === "true" + }); if (unzip) { const fileManager = new FileManager(instance.config.cwd); diff --git a/daemon/src/service/system_file.ts b/daemon/src/service/system_file.ts index 81ab418f..a7838ec3 100755 --- a/daemon/src/service/system_file.ts +++ b/daemon/src/service/system_file.ts @@ -251,4 +251,14 @@ export default class FileManager { } return true; } + + exists(uploadDir: string, fileName: string): boolean { + const fileSaveRelativePath = path.normalize(path.join(uploadDir, fileName)); + if (!FileManager.checkFileName(path.basename(fileName))) + throw new Error("Access denied: Malformed file name"); + if (!this.checkPath(fileSaveRelativePath)) + throw new Error("Access denied: Invalid destination"); + const fileSaveAbsolutePath = this.toAbsolutePath(fileSaveRelativePath); + return fs.existsSync(fileSaveAbsolutePath) + } } diff --git a/frontend/src/hooks/useFileManager.ts b/frontend/src/hooks/useFileManager.ts index 8d76efef..5bf54164 100644 --- a/frontend/src/hooks/useFileManager.ts +++ b/frontend/src/hooks/useFileManager.ts @@ -18,7 +18,8 @@ import { uploadAddress, uploadFile as uploadFileApi, downloadAddress, - changePermission as changePermissionApi + changePermission as changePermissionApi, + prepareUploadFile as prepareUploadFileApi } from "@/services/apis/fileManager"; import type { DataType, @@ -349,14 +350,17 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => { const selectedFile = async (file: File) => { const { execute: uploadFile } = uploadFileApi(); + const { state: prepareUploadFileResp, execute: prepareUploadFile } = prepareUploadFileApi(); const { state: uploadCfg, execute: getUploadCfg } = uploadAddress(); try { percentComplete.value = 1; + const uploadDir = breadcrumbs[breadcrumbs.length - 1].path; await getUploadCfg({ params: { - upload_dir: breadcrumbs[breadcrumbs.length - 1].path, + upload_dir: uploadDir, daemonId: daemonId!, - uuid: instanceId! + uuid: instanceId!, + file_name: file.name } }); if (!uploadCfg.value) { @@ -364,6 +368,48 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => { throw new Error(t("TXT_CODE_e8ce38c2")); } + var shouldOverwrite = false; + + await prepareUploadFile({ + params: { + daemonId: daemonId!, + uuid: instanceId!, + uploadDir: uploadDir, + uploadFilename: file.name + } + }).then((bl) => { + console.log(bl); + }); + + console.log(prepareUploadFileResp.value); + if (prepareUploadFileResp.value == undefined) { + percentComplete.value = 0; + throw new Error(t("TXT_CODE_4caa5237")); + } else if (prepareUploadFileResp.value.exists) { + var complete: (value: boolean) => void, reject: (reason?: any) => void; + var promise: Promise = new Promise((onComplete, onReject) => { + complete = onComplete; + reject = onReject; + }) + + Modal.confirm({ + title: t("TXT_CODE_99ca8563"), + icon: createVNode(ExclamationCircleOutlined), + content: t("TXT_CODE_ec99ddaa") + ` ${file.name} ` + t("TXT_CODE_8bd1f5d2"), + onOk() { + complete(true); + }, + onCancel() { + complete(false); + percentComplete.value = 0; + } + }); + shouldOverwrite = await promise; + if (!shouldOverwrite) { + return reportErrorMsg(t("TXT_CODE_8b14426e")); + } + } + const uploadFormData = new FormData(); uploadFormData.append("file", file); @@ -376,6 +422,9 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => { onUploadProgress: (progressEvent: any) => { const p = Math.round((progressEvent.loaded * 100) / progressEvent.total); if (p >= 1) percentComplete.value = p; + }, + params: { + overwrite: shouldOverwrite } }); await getFileList(); diff --git a/frontend/src/services/apis/fileManager.ts b/frontend/src/services/apis/fileManager.ts index 8aee2c58..69491301 100644 --- a/frontend/src/services/apis/fileManager.ts +++ b/frontend/src/services/apis/fileManager.ts @@ -153,7 +153,8 @@ export const uploadAddress = useDefineApi< params: { upload_dir: string; daemonId: string; - uuid: string; + uuid: string, + file_name: string; }; }, { @@ -167,7 +168,10 @@ export const uploadAddress = useDefineApi< export const uploadFile = useDefineApi< { - data: FormData; + data: FormData, + params: { + overwrite: boolean; + }; }, any >({ @@ -227,3 +231,20 @@ export const changePermission = useDefineApi< url: "/api/files/chmod", method: "PUT" }); + +export const prepareUploadFile = useDefineApi< + { + params: { + daemonId: string; + uuid: string, + uploadDir: string, + uploadFilename: string; + }; + }, + { + exists: boolean + } +>({ + url: "/api/files/pre_upload", + method: "GET" +}); diff --git a/frontend/src/widgets/setupApp/CreateInstanceForm.vue b/frontend/src/widgets/setupApp/CreateInstanceForm.vue index cc5eb66e..92ee67aa 100644 --- a/frontend/src/widgets/setupApp/CreateInstanceForm.vue +++ b/frontend/src/widgets/setupApp/CreateInstanceForm.vue @@ -161,7 +161,8 @@ const selectedFile = async () => { await getCfg({ params: { upload_dir: ".", - daemonId: props.daemonId + daemonId: props.daemonId, + file_name: uFile.value?.name }, data: formData }); diff --git a/languages/en_US.json b/languages/en_US.json index 40250526..7b717482 100644 --- a/languages/en_US.json +++ b/languages/en_US.json @@ -1901,5 +1901,10 @@ "TXT_CODE_930d2524": "Direct connection to web page", "TXT_CODE_e039b9b5": "normal", "TXT_CODE_a788e3eb": "Memory & Processor", - "TXT_CODE_c5ed896f": "The instantaneous output content is too long and has been rejected...." + "TXT_CODE_c5ed896f": "The instantaneous output content is too long and has been rejected....", + "TXT_CODE_4caa5237": "Failed to resolve file pre-upload status", + "TXT_CODE_99ca8563": "Overwriting File", + "TXT_CODE_ec99ddaa": "File", + "TXT_CODE_8bd1f5d2": "is already exists in this folder, should overwrite it?", + "TXT_CODE_8b14426e": "File already exists in this folder, Upload skipped" } diff --git a/languages/zh_CN.json b/languages/zh_CN.json index a0b1c259..c5e93673 100644 --- a/languages/zh_CN.json +++ b/languages/zh_CN.json @@ -1901,5 +1901,10 @@ "TXT_CODE_23a3bd72": "异常", "TXT_CODE_6b4a27dd": "网页前端无法与节点建立 WebSocket 连接,请检查网络代理配置或防火墙配置!", "TXT_CODE_a788e3eb": "内存 & 处理器", - "TXT_CODE_c5ed896f": "此刻瞬时输出内容过长,已拒绝显示..." + "TXT_CODE_c5ed896f": "此刻瞬时输出内容过长,已拒绝显示...", + "TXT_CODE_4caa5237": "获取文件存在状态失败", + "TXT_CODE_99ca8563": "覆盖文件", + "TXT_CODE_ec99ddaa": "您上传的文件", + "TXT_CODE_8bd1f5d2": "已经在目录中存在, 是否覆盖原文件?", + "TXT_CODE_8b14426e": "文件已存在, 跳过上传" } diff --git a/panel/src/app/routers/filemananger_router.ts b/panel/src/app/routers/filemananger_router.ts index b5808723..4c4912cf 100755 --- a/panel/src/app/routers/filemananger_router.ts +++ b/panel/src/app/routers/filemananger_router.ts @@ -316,6 +316,7 @@ router.all( const daemonId = String(ctx.query.daemonId); const instanceUuid = String(ctx.query.uuid); const uploadDir = String(ctx.query.upload_dir); + const uploadFilename = String(ctx.query.file_name); const remoteService = RemoteServiceSubsystem.getInstance(daemonId); const addr = `${remoteService?.config.ip}:${remoteService?.config.port}${ remoteService?.config.prefix ? removeTrail(remoteService.config.prefix, "/") : "" @@ -326,7 +327,8 @@ router.all( password: password, parameter: { uploadDir, - instanceUuid + instanceUuid, + uploadFilename } }); ctx.body = { @@ -339,4 +341,31 @@ router.all( } ); +router.get( + "/pre_upload", + permission({ level: ROLE.USER }), + validator({ + query: { daemonId: String, uuid: String, uploadDir: String, uploadFilename: String }, + }), + async (ctx) => { + try { + const daemonId = String(ctx.query.daemonId); + const instanceUuid = String(ctx.query.uuid); + const uploadDir = String(ctx.query.uploadDir); + const uploadFilename = String(ctx.query.uploadFilename); + const remoteService = RemoteServiceSubsystem.getInstance(daemonId); + const result = await new RemoteRequest(remoteService).request("file/exists", { + instanceUuid, + uploadDir, + uploadFilename + }); + ctx.body = { + exists: result + }; + } catch (err) { + ctx.body = err; + } + } +); + export default router;