From 9b7e569b3a6fa19992bb9d55e975c75ca090133a Mon Sep 17 00:00:00 2001 From: "meilin.huang" <954537473@qq.com> Date: Thu, 14 May 2026 21:29:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=BA=E5=99=A8=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=96=87=E4=BB=B6&=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E4=B8=8A=E4=BC=A0=E3=80=81=E6=94=AF=E6=8C=81=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 24 +- frontend/package.json | 1 - frontend/src/common/syssocket.ts | 10 + frontend/src/common/utils/file.ts | 16 + .../progress-notify/progress-notify.ts | 12 - .../db/DbSqlExecProgress.vue} | 11 +- .../sysmsg/db/db-sql-exec-progress.ts} | 27 +- frontend/src/components/sysmsg/db/index.ts | 5 + .../machine/MachineFileUploadProgress.vue | 128 ++++++ .../machine/MachineFolderUploadProgress.vue | 174 ++++++++ .../src/components/sysmsg/machine/index.ts | 7 + .../machine/machine-file-upload-progress.ts | 103 +++++ .../machine/machine-folder-upload-progress.ts | 115 +++++ .../src/components/terminal/TerminalBody.vue | 416 ++++++++++++++++-- .../components/terminal/TerminalDialog.vue | 4 + frontend/src/i18n/en/common.ts | 24 + frontend/src/i18n/zh-cn/common.ts | 24 + frontend/src/main.ts | 2 +- .../src/views/ops/db/component/DbDetail.vue | 3 + .../src/views/ops/machine/ScriptManage.vue | 10 +- frontend/src/views/ops/machine/api.ts | 201 +++++++++ .../ops/machine/component/MachineDetail.vue | 2 +- .../views/ops/machine/file/MachineFile.vue | 165 +++---- .../views/ops/machine/resource/MachineOp.vue | 4 + server/internal/machine/api/machine_file.go | 370 +++++++++++++--- server/internal/msg/application/dto/msg.go | 24 + server/internal/pkg/config/app.go | 2 +- 27 files changed, 1666 insertions(+), 218 deletions(-) create mode 100644 frontend/src/common/utils/file.ts delete mode 100644 frontend/src/components/progress-notify/progress-notify.ts rename frontend/src/components/{progress-notify/progress-notify.vue => sysmsg/db/DbSqlExecProgress.vue} (84%) rename frontend/src/{common/sysmsgs.ts => components/sysmsg/db/db-sql-exec-progress.ts} (74%) create mode 100644 frontend/src/components/sysmsg/db/index.ts create mode 100644 frontend/src/components/sysmsg/machine/MachineFileUploadProgress.vue create mode 100644 frontend/src/components/sysmsg/machine/MachineFolderUploadProgress.vue create mode 100644 frontend/src/components/sysmsg/machine/index.ts create mode 100644 frontend/src/components/sysmsg/machine/machine-file-upload-progress.ts create mode 100644 frontend/src/components/sysmsg/machine/machine-folder-upload-progress.ts diff --git a/Dockerfile b/Dockerfile index 391c15e7..c8655590 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,20 +7,34 @@ ARG MAYFLY_GO_VERSION ARG MAYFLY_GO_DIR_NAME=mayfly-go-linux-${TARGETARCH} ARG MAYFLY_GO_URL=https://gitee.com/dromara/mayfly-go/releases/download/${MAYFLY_GO_VERSION}/${MAYFLY_GO_DIR_NAME}.zip -RUN wget -cO mayfly-go.zip ${MAYFLY_GO_URL} && \ +RUN apk add --no-cache wget unzip && \ + wget -cO mayfly-go.zip ${MAYFLY_GO_URL} && \ unzip mayfly-go.zip && \ - mv ${MAYFLY_GO_DIR_NAME}/* /opt + cp -r ${MAYFLY_GO_DIR_NAME}/. /opt/ && \ + rm -rf mayfly-go.zip ${MAYFLY_GO_DIR_NAME} FROM ${BASEIMAGES} ARG TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -COPY --from=builder /opt/mayfly-go /usr/local/bin/mayfly-go +# 安装必要的运行时依赖并创建非root用户 +RUN apk add --no-cache ca-certificates tzdata && \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ + echo $TZ > /etc/timezone && \ + addgroup -g 1000 mayfly && \ + adduser -u 1000 -G mayfly -s /bin/sh -D mayfly +# 复制构建产物 +COPY --from=builder /opt/ /mayfly-go/ + +# 设置工作目录和权限 WORKDIR /mayfly-go +RUN chown -R mayfly:mayfly /mayfly-go + +# 切换到非root用户 +USER mayfly EXPOSE 18888 -CMD ["mayfly-go"] \ No newline at end of file +CMD ["./mayfly-go"] \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index b440875a..eaac2fb9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,7 +39,6 @@ "shiki-stream": "^0.1.4", "sortablejs": "^1.15.7", "sql-formatter": "^15.7.3", - "trzsz": "^1.1.6", "uuid": "^13.0.2", "vue": "3.6.0-beta.11", "vue-element-plus-x": "^2.0.2", diff --git a/frontend/src/common/syssocket.ts b/frontend/src/common/syssocket.ts index 0c78b909..4694f550 100644 --- a/frontend/src/common/syssocket.ts +++ b/frontend/src/common/syssocket.ts @@ -6,6 +6,16 @@ import { MsgSubtypeEnum } from './commonEnum'; import EnumValue from './Enum'; import { h } from 'vue'; import { MessageRenderer } from '@/components/message/message'; +import { initMachineSysMsgs } from '@/components/sysmsg/machine'; +import { initDbSysMsgs } from '@/components/sysmsg/db'; + +/** + * 初始化全局系统消息 + */ +export function initSysMsgs() { + initMachineSysMsgs(); + initDbSysMsgs(); +} class SysSocket { /** diff --git a/frontend/src/common/utils/file.ts b/frontend/src/common/utils/file.ts new file mode 100644 index 00000000..90b287c5 --- /dev/null +++ b/frontend/src/common/utils/file.ts @@ -0,0 +1,16 @@ +/** + * 下载文件 + * @param url 文件下载地址 + */ +export function downloadFile(url: string) { + // 使用隐藏的 iframe 下载,避免页面闪烁 + const iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = url; + document.body.appendChild(iframe); + + // 1秒后移除 iframe + setTimeout(() => { + document.body.removeChild(iframe); + }, 1000); +} diff --git a/frontend/src/components/progress-notify/progress-notify.ts b/frontend/src/components/progress-notify/progress-notify.ts deleted file mode 100644 index 0a7b3861..00000000 --- a/frontend/src/components/progress-notify/progress-notify.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const buildProgressProps = (): any => { - return { - progress: { - title: { - type: String, - }, - executedStatements: { - type: Number, - }, - }, - }; -}; diff --git a/frontend/src/components/progress-notify/progress-notify.vue b/frontend/src/components/sysmsg/db/DbSqlExecProgress.vue similarity index 84% rename from frontend/src/components/progress-notify/progress-notify.vue rename to frontend/src/components/sysmsg/db/DbSqlExecProgress.vue index 09527e6b..586b8ca1 100644 --- a/frontend/src/components/progress-notify/progress-notify.vue +++ b/frontend/src/components/sysmsg/db/DbSqlExecProgress.vue @@ -7,9 +7,16 @@ diff --git a/frontend/src/components/sysmsg/machine/MachineFolderUploadProgress.vue b/frontend/src/components/sysmsg/machine/MachineFolderUploadProgress.vue new file mode 100644 index 00000000..97f45dcb --- /dev/null +++ b/frontend/src/components/sysmsg/machine/MachineFolderUploadProgress.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/frontend/src/components/sysmsg/machine/index.ts b/frontend/src/components/sysmsg/machine/index.ts new file mode 100644 index 00000000..7a255090 --- /dev/null +++ b/frontend/src/components/sysmsg/machine/index.ts @@ -0,0 +1,7 @@ +import { registerMachineFileUploadProgress } from './machine-file-upload-progress'; +import { registerFolderUploadProgressHandler } from './machine-folder-upload-progress'; + +export function initMachineSysMsgs() { + registerMachineFileUploadProgress(); + registerFolderUploadProgressHandler(); +} diff --git a/frontend/src/components/sysmsg/machine/machine-file-upload-progress.ts b/frontend/src/components/sysmsg/machine/machine-file-upload-progress.ts new file mode 100644 index 00000000..babee8f6 --- /dev/null +++ b/frontend/src/components/sysmsg/machine/machine-file-upload-progress.ts @@ -0,0 +1,103 @@ +import syssocket from '@/common/syssocket'; +import { reactive, h } from 'vue'; +import { ElNotification } from 'element-plus'; +import MachineFileUploadProgress from './MachineFileUploadProgress.vue'; + +// 文件上传进度通知映射表(key: uploadId, value: 通知实例) +const fileUploadNotifyMap: Map = new Map(); + +/** + * 构建机器文件上传进度组件属性 + */ +const buildMachineFileUploadProgressProps = (): any => { + return { + progress: reactive({ + filename: '', + uploadedSize: 0, + totalSize: 0, + timestamp: 0, + status: '', // '' | 'success' | 'exception' + }), + }; +}; + +/** + * 注册机器文件上传进度消息处理 + */ +export async function registerMachineFileUploadProgress() { + await syssocket.registerMsgHandler('machineFileUploadProgress', function (message: any) { + const content = message.params; + const uploadId = content.uploadId; + + // 上传完成或失败,关闭通知 + if (content.status === 'complete' || content.status === 'error') { + const notify = fileUploadNotifyMap.get(uploadId); + + if (notify && notify.notification) { + // 更新最终状态 + notify.props.progress.status = content.status === 'complete' ? 'success' : 'exception'; + notify.props.progress.percent = content.status === 'complete' ? 100 : notify.props.progress.percent; + + // 强制更新 VNode + try { + if (notify.notification.state) { + notify.notification.state.message = h(MachineFileUploadProgress, notify.props); + } else if (notify.notification.vm) { + notify.notification.vm.exposed?.message?.(h(MachineFileUploadProgress, notify.props)); + } + } catch (e) { + console.warn('[MachineFileUpload] Failed to update notification VNode:', e); + } + + // 1秒后关闭通知 + setTimeout(() => { + if (notify.notification) { + notify.notification.close(); + } + fileUploadNotifyMap.delete(uploadId); + }, 1000); + } + return; + } + + // 获取或创建通知 + let notify = fileUploadNotifyMap.get(uploadId); + if (!notify) { + notify = { + props: buildMachineFileUploadProgressProps(), + notification: undefined, + }; + fileUploadNotifyMap.set(uploadId, notify); + } + + // 更新进度 + notify.props.progress.filename = content.filename || notify.props.progress.filename; + notify.props.progress.uploadedSize = content.uploadedSize || 0; + notify.props.progress.totalSize = content.totalSize || 0; + notify.props.progress.timestamp = content.timestamp || 0; + notify.props.progress.status = 'uploading'; + + // 首次创建通知 + if (!notify.notification) { + notify.notification = ElNotification({ + duration: 0, + title: message.title || '机器文件上传', + message: h(MachineFileUploadProgress, notify.props), + showClose: true, + offset: 60, + customClass: 'machine-file-upload-notification', + }); + } else { + // 已存在通知,强制更新 message + try { + if (notify.notification.state) { + notify.notification.state.message = h(MachineFileUploadProgress, notify.props); + } else if (notify.notification.vm) { + notify.notification.vm.exposed?.message?.(h(MachineFileUploadProgress, notify.props)); + } + } catch (e) { + console.warn('[MachineFileUpload] Failed to update notification VNode:', e); + } + } + }); +} diff --git a/frontend/src/components/sysmsg/machine/machine-folder-upload-progress.ts b/frontend/src/components/sysmsg/machine/machine-folder-upload-progress.ts new file mode 100644 index 00000000..a60d8cf0 --- /dev/null +++ b/frontend/src/components/sysmsg/machine/machine-folder-upload-progress.ts @@ -0,0 +1,115 @@ +import { ElNotification } from 'element-plus'; +import { h, reactive } from 'vue'; +import MachineFolderUploadProgress from '@/components/sysmsg/machine/MachineFolderUploadProgress.vue'; +import syssocket from '@/common/syssocket'; + +// 文件夹上传通知 Map +const folderUploadNotifyMap = new Map(); + +/** + * 构建文件夹上传进度组件的 props + */ +const buildMachineFolderUploadProgressProps = (): any => { + return { + progress: reactive({ + folderName: '', + totalFiles: 0, + uploadedFiles: 0, + totalSize: 0, + uploadedSize: 0, + lastFile: '', + uploadingFiles: [] as string[], + timestamp: 0, + status: '', + }), + }; +}; + +/** + * 注册文件夹上传进度消息处理 + */ +export async function registerFolderUploadProgressHandler() { + await syssocket.registerMsgHandler('machineFolderUploadProgress', function (message: any) { + const content = message.params; + const uploadId = content.uploadId; + + if (!uploadId) { + return; + } + + // 获取或创建通知 + let notify = folderUploadNotifyMap.get(uploadId); + + if (!notify) { + // 首次创建通知 + const props = buildMachineFolderUploadProgressProps(); + + const notificationInstance = ElNotification({ + title: '文件夹上传', + message: h(MachineFolderUploadProgress, props), + duration: 0, + position: 'top-right', + offset: 60, + customClass: 'machine-folder-upload-notify', + onClose: () => { + folderUploadNotifyMap.delete(uploadId); + }, + }); + + notify = { + props, + notification: notificationInstance, + }; + + folderUploadNotifyMap.set(uploadId, notify); + } + + // 上传完成或失败,关闭通知 + if (content.status === 'complete' || content.status === 'error') { + if (notify && notify.notification) { + // 更新最终状态 + notify.props.progress.status = content.status === 'complete' ? 'success' : 'exception'; + + // 强制更新 VNode + try { + if (notify.notification.state) { + notify.notification.state.message = h(MachineFolderUploadProgress, notify.props); + } + } catch (e) { + console.warn('[MachineFolderUpload] Failed to update notification VNode:', e); + } + + // 1秒后关闭通知 + setTimeout(() => { + if (notify.notification) { + notify.notification.close(); + } + folderUploadNotifyMap.delete(uploadId); + }, 1000); + } + return; + } + + // 更新进度 + if (content.status === 'uploading') { + notify.props.progress.folderName = content.folderName || ''; + notify.props.progress.totalFiles = content.totalFiles || 0; + notify.props.progress.uploadedFiles = content.uploadedFiles || 0; + notify.props.progress.totalSize = content.totalSize || 0; + notify.props.progress.uploadedSize = content.uploadedSize || 0; + notify.props.progress.lastFile = content.lastFile || ''; + notify.props.progress.uploadingFiles = content.uploadingFiles || []; + notify.props.progress.timestamp = content.timestamp || 0; + notify.props.progress.status = 'uploading'; + + // 强制更新 VNode + try { + if (notify.notification && notify.notification.state) { + notify.notification.state.message = h(MachineFolderUploadProgress, notify.props); + } + } catch (e) { + console.warn('[MachineFolderUpload] Failed to update notification VNode:', e); + } + } + }); +} diff --git a/frontend/src/components/terminal/TerminalBody.vue b/frontend/src/components/terminal/TerminalBody.vue index 9f45ee71..cb74fb1e 100644 --- a/frontend/src/components/terminal/TerminalBody.vue +++ b/frontend/src/components/terminal/TerminalBody.vue @@ -3,26 +3,36 @@
+ + +
- + diff --git a/frontend/src/components/terminal/TerminalDialog.vue b/frontend/src/components/terminal/TerminalDialog.vue index d19cf65f..395a95f3 100644 --- a/frontend/src/components/terminal/TerminalDialog.vue +++ b/frontend/src/components/terminal/TerminalDialog.vue @@ -78,6 +78,10 @@ :ref="(el) => setTerminalRef(el, openTerminal.terminalId)" :cmd="openTerminal.cmd" :socket-url="openTerminal.socketUrl" + :machine-id="openTerminal.meta?.id || 0" + :auth-cert-name="openTerminal.meta?.selectAuthCert?.name || ''" + :file-id="0" + :protocol="openTerminal.meta?.protocol || 1" /> diff --git a/frontend/src/i18n/en/common.ts b/frontend/src/i18n/en/common.ts index 96f1f34d..6849925f 100644 --- a/frontend/src/i18n/en/common.ts +++ b/frontend/src/i18n/en/common.ts @@ -270,6 +270,30 @@ export default { previous: 'Previous', next: 'Next', noMatchMsg: 'No matching item is found', + + // File transfer related + downloadFile: 'Download File', + downloadSelectedFile: 'Download Selected Path File', + uploadFile: 'Upload File', + uploadFileToCurrentDir: 'Upload File to Current Directory', + uploadFolder: 'Upload Folder', + uploadFolderToCurrentDir: 'Upload Folder to Current Directory', + chooseUploadType: 'Choose Upload Type', + startDownload: 'Start downloading file: {file}', + downloadFailed: 'File download failed: {error}', + uploadSuccess: 'File uploaded successfully', + uploadFailed: 'File upload failed: {error}', + uploading: 'Upload progress: {percent}%', + uploadToPath: 'File will be uploaded to: {path}', + uploadPathTip: 'Tip: File will be uploaded to home directory (~). To upload to another directory, use cd command first, then use drag-and-drop upload.', + + // Machine file upload progress notification + machineFileUpload: { + uploadProgress: 'Machine File Upload Progress', + uploaded: 'Uploaded', + totalSize: 'Total Size', + speed: 'Speed', + }, }, crontab: { crontabInputPlaceholder: 'Click the left button to configure', diff --git a/frontend/src/i18n/zh-cn/common.ts b/frontend/src/i18n/zh-cn/common.ts index 9df9c197..f65a337b 100644 --- a/frontend/src/i18n/zh-cn/common.ts +++ b/frontend/src/i18n/zh-cn/common.ts @@ -279,6 +279,30 @@ export default { previous: '上一个', next: '下一个', noMatchMsg: '未查询到匹配项', + + // 文件传输相关 + downloadFile: '下载文件', + downloadSelectedFile: '下载选中路径的文件', + uploadFile: '上传文件', + uploadFileToCurrentDir: '上传文件到当前目录', + uploadFolder: '上传文件夹', + uploadFolderToCurrentDir: '上传文件夹到当前目录', + chooseUploadType: '请选择上传类型', + startDownload: '开始下载文件: {file}', + downloadFailed: '文件下载失败: {error}', + uploadSuccess: '文件上传成功', + uploadFailed: '文件上传失败: {error}', + uploading: '上传进度: {percent}%', + uploadToPath: '文件将上传到路径: {path}', + uploadPathTip: '提示: 文件将上传到用户家目录(~)。如需上传到其他目录,请先在终端中执行 cd 命令切换到目标目录,然后使用拖拽上传功能。', + + // 机器文件上传进度通知 + machineFileUpload: { + uploadProgress: '机器文件上传进度', + uploaded: '已上传', + totalSize: '总大小', + speed: '速度', + }, }, crontab: { crontabInputPlaceholder: '可点击左边按钮配置', diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 393e4575..ee5072d3 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -16,7 +16,7 @@ import '@/theme/tailwind.css'; import '@/assets/font/font.css'; import '@/assets/icon/icon.js'; import { getThemeConfig } from './common/utils/storage'; -import { initSysMsgs } from './common/sysmsgs'; +import { initSysMsgs } from './common/syssocket'; const app = createApp(App); diff --git a/frontend/src/views/ops/db/component/DbDetail.vue b/frontend/src/views/ops/db/component/DbDetail.vue index 0d701b6f..4c34484b 100644 --- a/frontend/src/views/ops/db/component/DbDetail.vue +++ b/frontend/src/views/ops/db/component/DbDetail.vue @@ -11,6 +11,8 @@ {{ state.detail.code }} {{ state.detail.name }} + + {{ state.detail.host }}:{{ state.detail.port }} @@ -34,6 +36,7 @@ import { reactive } from 'vue'; import { dbApi } from '../api'; import { formatDate } from '@/common/utils/format'; import { getDbDialect } from '../dialect/index'; +import TagCodePath from '../../component/TagCodePath.vue'; const props = defineProps({ id: { diff --git a/frontend/src/views/ops/machine/ScriptManage.vue b/frontend/src/views/ops/machine/ScriptManage.vue index 11860ebe..33caefdc 100644 --- a/frontend/src/views/ops/machine/ScriptManage.vue +++ b/frontend/src/views/ops/machine/ScriptManage.vue @@ -72,7 +72,15 @@ draggable append-to-body > - + void; + /** 成功回调 */ + onSuccess?: () => void; + /** 错误回调 */ + onError?: (error: Error) => void; +} + +/** + * 上传单个文件 + * @param file 文件对象 + * @param params 上传参数 + * @param options 上传选项 + * @returns Promise + */ +export async function uploadFile(file: File, params: UploadParams, options: UploadOptions = {}): Promise { + const { onProgress, onSuccess, onError } = options; + + const formData = new FormData(); + formData.append('file', file); + formData.append('uploadId', params.uploadId); + formData.append('machineId', String(params.machineId)); + formData.append('authCertName', params.authCertName); + formData.append('protocol', String(params.protocol)); + formData.append('fileId', String(params.fileId)); + formData.append('path', params.path); + + if (params.relativePath) { + formData.append('relativePath', params.relativePath); + } + + const token = getToken(); + const url = `${config.baseApiUrl}/machines/${params.machineId}/files/${params.fileId}/upload?token=${token}`; + + try { + const xhr = new XMLHttpRequest(); + + // 进度回调 + xhr.upload.onprogress = (event) => { + if (event.lengthComputable && onProgress) { + const percent = Math.round((event.loaded / event.total) * 100); + const elapsed = (Date.now() - startTime) / 1000; + const speedBytes = elapsed > 0 ? event.loaded / elapsed : 0; + let speed = '0 B/s'; + if (speedBytes < 1024) { + speed = `${speedBytes.toFixed(0)} B/s`; + } else if (speedBytes < 1024 * 1024) { + speed = `${(speedBytes / 1024).toFixed(1)} KB/s`; + } else { + speed = `${(speedBytes / (1024 * 1024)).toFixed(1)} MB/s`; + } + onProgress(percent, event.loaded, event.total, speed); + } + }; + + // 完成回调 + xhr.onload = () => { + if (xhr.status === 200) { + onSuccess?.(); + } else { + onError?.(new Error(t('common.uploadFailed', { error: `HTTP ${xhr.status}` }))); + } + }; + + // 错误回调 + xhr.onerror = () => { + onError?.(new Error(t('common.uploadFailed', { error: '网络错误' }))); + }; + + const startTime = Date.now(); + xhr.open('POST', url); + xhr.send(formData); + } catch (error: any) { + onError?.(new Error(t('common.uploadFailed', { error: error.message }))); + } +} + +/** + * 文件夹上传参数 + */ +export interface FolderUploadParams { + /** 上传ID(前端生成,保证唯一性) */ + uploadId: string; + /** 机器ID */ + machineId: number; + /** 认证证书名称 */ + authCertName: string; + /** 协议类型 */ + protocol: number; + /** 文件ID */ + fileId: number; + /** 目标路径 */ + path: string; +} + +/** + * 文件夹上传选项 + */ +export interface FolderUploadOptions { + /** 成功回调 */ + onSuccess?: () => void; + /** 错误回调 */ + onError?: (error: Error) => void; +} + +/** + * 上传文件夹(使用 /upload-folder 接口) + * @param files 文件列表 + * @param params 上传参数 + * @param options 上传选项 + * @returns Promise + */ +export async function uploadFolder(files: FileList | File[], params: FolderUploadParams, options: FolderUploadOptions = {}): Promise { + const { onSuccess, onError } = options; + + const formData = new FormData(); + formData.append('uploadId', params.uploadId); + formData.append('basePath', params.path); + formData.append('machineId', String(params.machineId)); + formData.append('authCertName', params.authCertName); + formData.append('protocol', String(params.protocol)); + + // 添加所有文件 + const paths: string[] = []; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const relativePath = (file as any).webkitRelativePath || file.name; + formData.append('files', file); + paths.push(relativePath); + } + + // 添加路径数组 + paths.forEach((path) => { + formData.append('paths', path); + }); + + const token = getToken(); + const url = `${config.baseApiUrl}/machines/${params.machineId}/files/${params.fileId}/upload-folder?token=${token}`; + + try { + const xhr = new XMLHttpRequest(); + + // 完成回调 + xhr.onload = () => { + if (xhr.status === 200) { + onSuccess?.(); + } else { + onError?.(new Error(t('common.uploadFailed', { error: `HTTP ${xhr.status}` }))); + } + }; + + // 错误回调 + xhr.onerror = () => { + onError?.(new Error(t('common.uploadFailed', { error: '网络错误' }))); + }; + + xhr.open('POST', url); + xhr.send(formData); + } catch (error: any) { + onError?.(new Error(t('common.uploadFailed', { error: error.message }))); + } +} diff --git a/frontend/src/views/ops/machine/component/MachineDetail.vue b/frontend/src/views/ops/machine/component/MachineDetail.vue index 0da0a080..af2339e2 100644 --- a/frontend/src/views/ops/machine/component/MachineDetail.vue +++ b/frontend/src/views/ops/machine/component/MachineDetail.vue @@ -11,7 +11,7 @@ {{ state.machineDetail.code }} {{ state.machineDetail.name }} - + {{ state.machineDetail.ip }} {{ state.machineDetail.port }} diff --git a/frontend/src/views/ops/machine/file/MachineFile.vue b/frontend/src/views/ops/machine/file/MachineFile.vue index 56dd8bfd..b5c28979 100755 --- a/frontend/src/views/ops/machine/file/MachineFile.vue +++ b/frontend/src/views/ops/machine/file/MachineFile.vue @@ -1,9 +1,6 @@