Corrigir ícones e adicionar exclusão no explorador de arquivos
- 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
This commit is contained in:
@@ -144,6 +144,7 @@ const API = {
|
|||||||
|
|
||||||
files: {
|
files: {
|
||||||
list(path) { return API.request('GET', `/files${path ? '?path=' + encodeURIComponent(path) : ''}`); },
|
list(path) { return API.request('GET', `/files${path ? '?path=' + encodeURIComponent(path) : ''}`); },
|
||||||
|
delete(path) { return API.request('DELETE', `/files?path=${encodeURIComponent(path)}`); },
|
||||||
},
|
},
|
||||||
|
|
||||||
reports: {
|
reports: {
|
||||||
|
|||||||
@@ -774,6 +774,7 @@ const App = {
|
|||||||
case 'navigate-files': FilesUI.navigate(path || ''); break;
|
case 'navigate-files': FilesUI.navigate(path || ''); break;
|
||||||
case 'download-file': FilesUI.downloadFile(path); break;
|
case 'download-file': FilesUI.downloadFile(path); break;
|
||||||
case 'download-folder': FilesUI.downloadFolder(path); break;
|
case 'download-folder': FilesUI.downloadFolder(path); break;
|
||||||
|
case 'delete-entry': FilesUI.deleteEntry(path, el.dataset.entryType); break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const FilesUI = {
|
|||||||
${breadcrumb}
|
${breadcrumb}
|
||||||
<div class="files-toolbar">
|
<div class="files-toolbar">
|
||||||
<span class="files-count">${entries.length} ${entries.length === 1 ? 'item' : 'itens'}</span>
|
<span class="files-count">${entries.length} ${entries.length === 1 ? 'item' : 'itens'}</span>
|
||||||
<button class="btn btn--ghost btn--sm" data-action="download-folder" data-path="${Utils.escapeHtml(data.path || '')}" title="Baixar pasta como .tar.gz"><i data-lucide="archive" style="width:14px;height:14px"></i> Baixar tudo</button>
|
<button class="btn btn--ghost btn--sm" data-action="download-folder" data-path="${Utils.escapeHtml(data.path || '')}" title="Baixar pasta como .tar.gz"><i data-lucide="download" style="width:14px;height:14px"></i> Baixar tudo</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="files-table-wrapper">
|
<div class="files-table-wrapper">
|
||||||
<table class="files-table">
|
<table class="files-table">
|
||||||
@@ -87,9 +87,11 @@ const FilesUI = {
|
|||||||
? `<a href="#" class="files-entry-link files-entry-dir" data-action="navigate-files" data-path="${Utils.escapeHtml(fullPath)}"><i data-lucide="${icon}" style="width:16px;height:16px;color:${iconColor};flex-shrink:0"></i> ${Utils.escapeHtml(entry.name)}</a>`
|
? `<a href="#" class="files-entry-link files-entry-dir" data-action="navigate-files" data-path="${Utils.escapeHtml(fullPath)}"><i data-lucide="${icon}" style="width:16px;height:16px;color:${iconColor};flex-shrink:0"></i> ${Utils.escapeHtml(entry.name)}</a>`
|
||||||
: `<span class="files-entry-link files-entry-file"><i data-lucide="${icon}" style="width:16px;height:16px;color:${iconColor};flex-shrink:0"></i> ${Utils.escapeHtml(entry.name)}</span>`;
|
: `<span class="files-entry-link files-entry-file"><i data-lucide="${icon}" style="width:16px;height:16px;color:${iconColor};flex-shrink:0"></i> ${Utils.escapeHtml(entry.name)}</span>`;
|
||||||
|
|
||||||
const actions = entry.type === 'directory'
|
const downloadBtn = entry.type === 'directory'
|
||||||
? `<button class="btn btn--ghost btn--sm" data-action="download-folder" data-path="${Utils.escapeHtml(fullPath)}" title="Baixar pasta"><i data-lucide="archive" style="width:14px;height:14px"></i></button>`
|
? `<button class="btn btn--ghost btn--sm" data-action="download-folder" data-path="${Utils.escapeHtml(fullPath)}" title="Baixar pasta"><i data-lucide="download" style="width:14px;height:14px"></i></button>`
|
||||||
: `<button class="btn btn--ghost btn--sm" data-action="download-file" data-path="${Utils.escapeHtml(fullPath)}" title="Baixar arquivo"><i data-lucide="download" style="width:14px;height:14px"></i></button>`;
|
: `<button class="btn btn--ghost btn--sm" data-action="download-file" data-path="${Utils.escapeHtml(fullPath)}" title="Baixar arquivo"><i data-lucide="download" style="width:14px;height:14px"></i></button>`;
|
||||||
|
const deleteBtn = `<button class="btn btn--ghost btn--sm btn-danger" data-action="delete-entry" data-path="${Utils.escapeHtml(fullPath)}" data-entry-type="${entry.type}" title="Excluir"><i data-lucide="trash-2" style="width:14px;height:14px"></i></button>`;
|
||||||
|
const actions = `${downloadBtn}${deleteBtn}`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="files-row">
|
<tr class="files-row">
|
||||||
@@ -147,6 +149,24 @@ const FilesUI = {
|
|||||||
a.download = '';
|
a.download = '';
|
||||||
a.click();
|
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;
|
window.FilesUI = FilesUI;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as pipeline from '../agents/pipeline.js';
|
|||||||
import { getBinPath, updateMaxConcurrent, cancelAllExecutions, getActiveExecutions } from '../agents/executor.js';
|
import { getBinPath, updateMaxConcurrent, cancelAllExecutions, getActiveExecutions } from '../agents/executor.js';
|
||||||
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
||||||
import { cached } from '../cache/index.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 { join, dirname, resolve as pathResolve, extname, basename, relative } from 'path';
|
||||||
import { createGzip } from 'zlib';
|
import { createGzip } from 'zlib';
|
||||||
import { Readable } from 'stream';
|
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;
|
export default router;
|
||||||
|
|||||||
Reference in New Issue
Block a user