From 72042598b0e89c5796cdf20e4d5ebb19cfbd06c2 Mon Sep 17 00:00:00 2001 From: Nexus Agent Date: Sat, 13 Jun 2026 09:08:17 +0800 Subject: [PATCH] Fix files page new-file flow and SFTP parent directory creation. Show a clear prompt when no server is selected, wait for the editor workbench before openFile, and mkdir -p parent paths before SFTP writes. --- docs/audit/2026-06-10-files-new-file-fix.md | 35 +++++++++++++++++++ .../2026-06-10-files-new-file-fix.md | 25 +++++++++++++ .../src/components/files/FilesToolbar.vue | 3 +- .../src/composables/files/useFilesActions.ts | 24 +++++++++++-- .../src/composables/files/useFilesEditor.ts | 1 + .../src/composables/files/useFilesPage.ts | 2 ++ server/infrastructure/ssh/asyncssh_pool.py | 3 ++ 7 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 docs/audit/2026-06-10-files-new-file-fix.md create mode 100644 docs/changelog/2026-06-10-files-new-file-fix.md diff --git a/docs/audit/2026-06-10-files-new-file-fix.md b/docs/audit/2026-06-10-files-new-file-fix.md new file mode 100644 index 00000000..1845552a --- /dev/null +++ b/docs/audit/2026-06-10-files-new-file-fix.md @@ -0,0 +1,35 @@ +# 审计 — 文件管理「新建文件」修复 + +**Changelog**: `docs/changelog/2026-06-10-files-new-file-fix.md` + +## 变更文件 + +| 文件 | 说明 | +|------|------| +| `frontend/src/composables/files/useFilesActions.ts` | `openNewFileDialog`、等待编辑器就绪 | +| `frontend/src/composables/files/useFilesEditor.ts` | 导出 `waitForEditorWorkbench` | +| `frontend/src/composables/files/useFilesPage.ts` | 透传 helper | +| `frontend/src/components/files/FilesToolbar.vue` | 未选服务器时提示 | +| `server/infrastructure/ssh/asyncssh_pool.py` | SFTP 写前确保父目录 | + +## Step 3(规则扫描) + +| 规则 | 结论 | 说明 | +|------|------|------| +| 鉴权 | PASS | 仍走 `/sync/write-file` + admin JWT | +| 路径 | PASS | `validatePathSegment` 不变 | +| 静默失败 | PASS | 编辑器未就绪有 warning snackbar | +| SSH | PASS | `mkdir -p` 仅父目录,不扩大写范围 | + +## Closure + +| 文件 | 结论 | +|------|------| +| `useFilesActions.ts` | SAFE — 无 v-html;错误经 snackbar | +| `asyncssh_pool.py` | SAFE — 复用已有 `_ensure_remote_parent_dir` | + +## DoD + +- [x] changelog + 本审计 +- [x] frontend type-check +- [ ] 生产:文件管理新建文件 → 编辑器打开 diff --git a/docs/changelog/2026-06-10-files-new-file-fix.md b/docs/changelog/2026-06-10-files-new-file-fix.md new file mode 100644 index 00000000..f09950e9 --- /dev/null +++ b/docs/changelog/2026-06-10-files-new-file-fix.md @@ -0,0 +1,25 @@ +# 2026-06-10 — 文件管理「新建文件」修复 + +## 摘要 + +修复文件页「新建文件」点击无反馈或创建后编辑器不弹出的问题。 + +## 动机 + +- 未选服务器时按钮被禁用,用户以为功能坏了 +- 创建成功后未等待 `FileEditorWorkbench` 就绪即 `openFile`,编辑器静默不打开 +- SFTP 写入前未确保父目录存在(部分路径下 write-file 失败) + +## 涉及文件 + +- `frontend/src/composables/files/useFilesActions.ts` — `openNewFileDialog`、`waitForEditorWorkbench` +- `frontend/src/composables/files/useFilesEditor.ts` — 导出 `waitForEditorWorkbench` +- `frontend/src/components/files/FilesToolbar.vue` — 未选服务器时提示而非禁用 +- `server/infrastructure/ssh/asyncssh_pool.py` — SFTP 写前 `mkdir -p` 父目录 + +## 验证 + +```bash +cd frontend && npm run type-check +# 文件管理 → 选服务器 → 新建文件 → 应创建并打开编辑器 +``` diff --git a/frontend/src/components/files/FilesToolbar.vue b/frontend/src/components/files/FilesToolbar.vue index 3cfb0a69..cdd9c024 100644 --- a/frontend/src/components/files/FilesToolbar.vue +++ b/frontend/src/components/files/FilesToolbar.vue @@ -97,8 +97,7 @@ const p = useFilesPageContext() size="small" variant="tonal" prepend-icon="mdi-file-plus" - :disabled="!p.selectedServer" - @click="p.showNewFile = true" + @click="p.openNewFileDialog()" > 新建文件 diff --git a/frontend/src/composables/files/useFilesActions.ts b/frontend/src/composables/files/useFilesActions.ts index cc590bf9..879a00b3 100644 --- a/frontend/src/composables/files/useFilesActions.ts +++ b/frontend/src/composables/files/useFilesActions.ts @@ -26,6 +26,7 @@ export function useFilesActions(options: { previewFile: (item: FileEntry) => Promise openInTerminal: () => void editorWorkbench: Ref<{ openFile: (p: OpenFilePayload) => void } | null> + waitForEditorWorkbench?: () => Promise<{ openFile: (p: OpenFilePayload) => void } | null> actionLoading?: Ref }) { const snackbar = useSnackbar() @@ -289,7 +290,11 @@ export function useFilesActions(options: { } async function doNewFile() { - if (!selectedServer.value || !newFileName.value) return + if (!selectedServer.value) { + snackbar('请先选择服务器', 'warning') + return + } + if (!newFileName.value) return const check = validatePathSegment(newFileName.value) if (check !== true) { snackbar(check, 'warning') @@ -311,7 +316,13 @@ export function useFilesActions(options: { await options.browse({ force: true }) snackbar('文件已创建') await nextTick() - options.editorWorkbench.value?.openFile({ + const wb = + (await options.waitForEditorWorkbench?.()) ?? options.editorWorkbench.value + if (!wb?.openFile) { + snackbar('文件已创建;编辑器未就绪,请从列表双击打开', 'warning') + return + } + wb.openFile({ path, name, content: '', @@ -541,6 +552,14 @@ export function useFilesActions(options: { showContextMenu.value = true } + function openNewFileDialog() { + if (!selectedServer.value) { + snackbar('请先选择服务器', 'warning') + return + } + showNewFile.value = true + } + return { dropActive, showUpload, @@ -580,6 +599,7 @@ export function useFilesActions(options: { doCompress, doDecompress, doNewFile, + openNewFileDialog, startRename, doRename, startChmod, diff --git a/frontend/src/composables/files/useFilesEditor.ts b/frontend/src/composables/files/useFilesEditor.ts index 21df3e81..82db03ce 100644 --- a/frontend/src/composables/files/useFilesEditor.ts +++ b/frontend/src/composables/files/useFilesEditor.ts @@ -155,6 +155,7 @@ export function useFilesEditor(options: { previewContent, previewLoading, editorWorkbench, + waitForEditorWorkbench, previewFile, editFile, onEditorSaved, diff --git a/frontend/src/composables/files/useFilesPage.ts b/frontend/src/composables/files/useFilesPage.ts index 7f78be63..9b3be327 100644 --- a/frontend/src/composables/files/useFilesPage.ts +++ b/frontend/src/composables/files/useFilesPage.ts @@ -89,6 +89,7 @@ export function useFilesPage() { previewFile: editor.previewFile, openInTerminal: nav.openInTerminal, editorWorkbench: editor.editorWorkbench, + waitForEditorWorkbench: editor.waitForEditorWorkbench, actionLoading, }) @@ -228,6 +229,7 @@ export function useFilesPage() { showNewFile: actions.showNewFile, newFileName: actions.newFileName, doNewFile: actions.doNewFile, + openNewFileDialog: actions.openNewFileDialog, showMkdir: actions.showMkdir, mkdirName: actions.mkdirName, doMkdir: actions.doMkdir, diff --git a/server/infrastructure/ssh/asyncssh_pool.py b/server/infrastructure/ssh/asyncssh_pool.py index 0efc353c..309960dc 100644 --- a/server/infrastructure/ssh/asyncssh_pool.py +++ b/server/infrastructure/ssh/asyncssh_pool.py @@ -419,6 +419,9 @@ async def remote_write_bytes( if not skip_sftp: try: + parent = posix_dirname(remote_path) + if parent and parent != "/": + await _ensure_remote_parent_dir(conn, remote_path, use_sudo=False) async with await conn.start_sftp_client() as sftp: await sftp_write_bytes(sftp, remote_path, data) return