From e9f65c2845ef90a08e43785e293553ce0ee3926c Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Sat, 28 Feb 2026 03:00:45 -0300 Subject: [PATCH] =?UTF-8?q?Corrigir=20=C3=ADcones=20e=20adicionar=20exclus?= =?UTF-8?q?=C3=A3o=20no=20explorador=20de=20arquivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Trocar ícone archive (lixeira) por download em todos os botões - Adicionar botão de excluir com ícone trash-2 em cada entrada - Rota DELETE /api/files com proteção contra exclusão da raiz - Confirmação via modal antes de excluir --- public/js/api.js | 1 + public/js/app.js | 1 + public/js/components/files.js | 26 +++++++++++++++++++++++--- src/routes/api.js | 22 +++++++++++++++++++++- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/public/js/api.js b/public/js/api.js index 813618d..32cabec 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -144,6 +144,7 @@ const API = { files: { list(path) { return API.request('GET', `/files${path ? '?path=' + encodeURIComponent(path) : ''}`); }, + delete(path) { return API.request('DELETE', `/files?path=${encodeURIComponent(path)}`); }, }, reports: { diff --git a/public/js/app.js b/public/js/app.js index f15da09..153e444 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -774,6 +774,7 @@ const App = { case 'navigate-files': FilesUI.navigate(path || ''); break; case 'download-file': FilesUI.downloadFile(path); break; case 'download-folder': FilesUI.downloadFolder(path); break; + case 'delete-entry': FilesUI.deleteEntry(path, el.dataset.entryType); break; } }); diff --git a/public/js/components/files.js b/public/js/components/files.js index 268ac14..79183d4 100644 --- a/public/js/components/files.js +++ b/public/js/components/files.js @@ -40,7 +40,7 @@ const FilesUI = { ${breadcrumb}
${entries.length} ${entries.length === 1 ? 'item' : 'itens'} - +
@@ -87,9 +87,11 @@ const FilesUI = { ? ` ${Utils.escapeHtml(entry.name)}` : ` ${Utils.escapeHtml(entry.name)}`; - const actions = entry.type === 'directory' - ? `` + const downloadBtn = entry.type === 'directory' + ? `` : ``; + const deleteBtn = ``; + const actions = `${downloadBtn}${deleteBtn}`; return ` @@ -147,6 +149,24 @@ const FilesUI = { a.download = ''; a.click(); }, + + async deleteEntry(path, entryType) { + const label = entryType === 'directory' ? 'pasta' : 'arquivo'; + const name = path.split('/').pop(); + const confirmed = await Modal.confirm( + `Excluir ${label}`, + `Tem certeza que deseja excluir "${name}"? Esta ação não pode ser desfeita.` + ); + if (!confirmed) return; + + try { + await API.files.delete(path); + Toast.success(`${label.charAt(0).toUpperCase() + label.slice(1)} excluído`); + await FilesUI.navigate(FilesUI.currentPath); + } catch (err) { + Toast.error(`Erro ao excluir: ${err.message}`); + } + }, }; window.FilesUI = FilesUI; diff --git a/src/routes/api.js b/src/routes/api.js index 6a65769..ba4c4e1 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -11,7 +11,7 @@ import * as pipeline from '../agents/pipeline.js'; import { getBinPath, updateMaxConcurrent, cancelAllExecutions, getActiveExecutions } from '../agents/executor.js'; import { invalidateAgentMapCache } from '../agents/pipeline.js'; import { cached } from '../cache/index.js'; -import { readdirSync, readFileSync, unlinkSync, existsSync, mkdirSync, statSync, createReadStream } from 'fs'; +import { readdirSync, readFileSync, unlinkSync, existsSync, mkdirSync, statSync, createReadStream, rmSync } from 'fs'; import { join, dirname, resolve as pathResolve, extname, basename, relative } from 'path'; import { createGzip } from 'zlib'; import { Readable } from 'stream'; @@ -1140,4 +1140,24 @@ router.get('/files/download-folder', (req, res) => { } }); +router.delete('/files', (req, res) => { + try { + const targetPath = resolveProjectPath(req.query.path || ''); + if (!targetPath) return res.status(400).json({ error: 'Caminho inválido' }); + if (targetPath === PROJECTS_DIR) return res.status(400).json({ error: 'Não é permitido excluir o diretório raiz' }); + if (!existsSync(targetPath)) return res.status(404).json({ error: 'Arquivo ou pasta não encontrado' }); + + const stat = statSync(targetPath); + if (stat.isDirectory()) { + rmSync(targetPath, { recursive: true, force: true }); + } else { + unlinkSync(targetPath); + } + + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + export default router;