proside / file-manager
File Manager reutilizável para Laravel, construído em Livewire v4 (substitui o File Manager Next.js da Proside).
Package info
github.com/leonardo-cheira-proside/file-manager
Language:Blade
pkg:composer/proside/file-manager
Requires
- php: ^8.1
- illuminate/filesystem: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- livewire/livewire: ^4.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Gestor de ficheiros reutilizável para Laravel 10/11/12 construído integralmente em Livewire v4. Substitui o antigo File Manager em Next.js/React, mantendo paridade funcional e melhorando a arquitetura: sem iframe, sem JWT, sem serviço externo — corre dentro da própria aplicação Laravel e usa a autenticação/sessão existente.
Compatível com Laravel 10.49 e PHP 8.1+. Requer Livewire ^4.0.
Funcionalidades
- Árvore de diretórios lateral com lazy-load e expandir/colapsar
- Vistas em grelha e lista
- Filtros (Tudo / Pastas / Imagens / Vídeos) e ordenação (A–Z / Z–A)
- Breadcrumbs de navegação
- Seleção simples e múltipla (Shift)
- Menu de contexto: criar subpasta, renomear, eliminar, visualizar, descarregar, escolher
- Upload por botão flutuante (FAB) e por arrastar ficheiros do sistema
- Mover por drag & drop (incluindo múltiplos itens) para pastas ou para a árvore
- Lixo (
apagados) com retenção configurável e expiração automática - Restaurar itens do lixo (novo) e eliminação definitiva
- Pré-visualização (lightbox) de imagens e vídeos
- Pesquisa funcional por nome (o FM antigo não pesquisava)
- Pronto para picker de ficheiros em formulários (
<x-file-manager::picker>) - Multi-disco (local/public, S3, …) via Filesystem do Laravel
- Copiar URL de um ficheiro a partir do menu de contexto
- Traduções PT/EN, totalmente publicáveis
Instalação
composer require proside/file-manager
O Service Provider, rotas, componente Livewire e componentes Blade são registados automaticamente (package discovery).
1. Garantir o disco e o symlink
Por omissão usa o disco public. Crie o symlink uma vez:
php artisan storage:link
2. (Opcional) Publicar recursos
php artisan vendor:publish --tag=file-manager-config # config/file-manager.php php artisan vendor:publish --tag=file-manager-views # resources/views/vendor/file-manager php artisan vendor:publish --tag=file-manager-lang # lang/vendor/file-manager php artisan vendor:publish --tag=file-manager-assets # public/vendor/file-manager/file-manager.css
3. Tailwind (importante)
O package usa classes utilitárias Tailwind (paleta proximo, equivalente ao antigo proximo).
Se a sua app compila Tailwind, adicione o caminho das vistas do package ao content do
tailwind.config.js para que as classes sejam geradas:
content: [ // ... './vendor/proside/file-manager/resources/views/**/*.blade.php', ],
Tailwind v4 não usa o content do tailwind.config.js e ignora por omissão
tudo o que está no .gitignore — ou seja, o vendor/. No ficheiro CSS de
entrada da app:
@import "tailwindcss"; @source "../../vendor/proside/file-manager/resources/views";
Recompile (npm run build) depois de cada composer update do package,
senão as classes novas não entram no CSS e a UI aparece meio por estilizar.
4. Agendar a limpeza do lixo
Em app/Console/Kernel.php:
$schedule->command('file-manager:prune-trash')->daily();
Utilização
Página inteira
Já existe a rota GET /file-manager (configurável). Ou embeba o componente onde quiser:
<div style="height: 80vh"> <livewire:file-manager /> </div>
Picker em formulários (drop-in)
Substitui o antigo <x-file-manager-modal> com a mesma API de props:
{{-- Um ficheiro --}} <x-file-manager::picker input-name="gfqueue_image" :value="$queue->image ?? ''" /> {{-- Vários ficheiros --}} <x-file-manager::picker input-name="imagens[]" :value="$existing" multiple /> {{-- Forçar apenas imagens (ou 'videos') --}} <x-file-manager::picker input-name="icon" filter="images" />
O ficheiro escolhido fica num <input type="hidden" name="..."> — o submit do formulário
funciona exatamente como antes. Ao escolher, o componente emite o evento Livewire
file-manager-selected ({ paths: [...] }).
Permissões por utilizador (scoping)
O File Manager pode confinar cada utilizador a uma ou mais pastas-raiz próprias.
Define um resolver que devolve a(s) raiz(es) efetiva(s) do utilizador atual
(relativas ao disco), ou null para acesso total:
// config/file-manager.php 'root_resolver' => \App\FileManager\LevelRootResolver::class, // invocável: __invoke(): null|string|array
Regras aplicadas pelo package quando há resolver:
- devolve
null,'', ou um valor igual à raiz da config → acesso total; - devolve
"conteudos/optivisao"→ o utilizador só vê essa pasta para baixo (árvore, breadcrumbs, navegação e operações ficam confinadas; aceder acima é bloqueado porPathGuard). - devolve um array (ex.:
['conteudos/a', 'conteudos/b']) → o utilizador vê várias raízes: cada uma aparece na sidebar com a sua árvore, abre-se na primeira, e a navegação fica confinada a qualquer uma delas. Caminhos fora da raiz da config são ignorados (não escalam privilégios).
O lixo espelha a árvore de origem
conteudos/optivisao/fotos/x.png é eliminado para
apagados/conteudos/optivisao/fotos/x.png. Isto tem três consequências:
- Confinamento por prefixo. O lixo de cada raiz é um ramo próprio, validado
pelo
PathGuardcomo qualquer outro caminho. Um utilizador confinado não consegue ler, restaurar nem eliminar definitivamente o que outro apagou. - Restauro sem metadados. O destino vem do próprio caminho, e é revalidado — um item não pode ser restaurado para fora das raízes do utilizador.
- Sem colisões.
a/relatorio.txteb/relatorio.txtdeixam de disputar o mesmo nome no lixo.
O sidecar .meta.json mantém-se, mas só guarda o prazo de expiração.
Migração. Instalações que já tenham itens no lixo antigo (plano, em
apagados/<ficheiro>) deixam de os ver — o acesso falha fechado, nada é perdido. Para os recuperar, mova cada ficheiro para o caminho indicado no respetivooriginalPathprefixado porapagados/, ou esvazie o lixo antes de atualizar.
Copiar URL
O menu de contexto de um ficheiro tem Copiar URL, que copia o endereço de
media (…/file-manager/media/conteudos/…) para a área de transferência.
A cópia é feita inteiramente no cliente: o URL já vem em cada item da listagem, por isso não há pedido ao servidor. É um endereço público e permanente — vale o que a rota de media valer (ver a nota mais abaixo).
Auditoria
As operações que alteram ficheiros (upload, mover, copiar, duplicar, renomear,
criar pasta, eliminar, restaurar, eliminar definitivamente, partilhar, prune)
são registadas via o logger do Laravel, com utilizador e IP. Para as separar do
log da aplicação, defina um canal em config/logging.php e aponte
file-manager.audit.channel para ele.
Exemplo de resolver (Backoffice Proside — perfil gflevel ligado por gfleveluser,
campo gflevel_ctvdir): ver docs/BACKOFFICE-INTEGRATION.md.
O resolver pode ser um class-string invocável (compatível com config:cache) ou um callable.
Configuração (config/file-manager.php)
| Chave | Omissão | Descrição |
|---|---|---|
disk |
public |
Disco do Filesystem |
root |
conteudos |
Pasta base navegável |
trash |
apagados |
Pasta do lixo |
trash_retention_days |
30 |
Dias até eliminação definitiva |
root_resolver |
null |
Resolver da raiz por utilizador (scoping); null = acesso total |
uploads.max_size |
51200 (KB) |
Tamanho máximo por ficheiro |
uploads.mimes |
null |
Mimes aceites (null = todos) |
media_url |
route |
route (seguro, qualquer disco/nome) / storage (direto, mais rápido) / auto |
route.middleware |
web, auth |
Middleware do gestor/upload/download |
route.media_middleware |
web |
Middleware da rota de media — pública por omissão (ver conteúdo sem auth) |
route.redirect_on_error |
dashboard |
Para onde redireciona se o root_resolver falhar (evita erro 500) |
route.* |
— | Prefixo, middleware e rota full-page |
attachment_extensions |
svg, html, xml, js, php, … |
Tipos sempre servidos como download — impede que um SVG com <script> corra na origem da app |
search.cache_seconds |
10 |
Cache do varrimento da pesquisa (0 desliga) |
audit.enabled |
true |
Regista as operações que alteram ficheiros |
audit.channel |
null |
Canal de log da auditoria (null = canal por omissão da app) |
Variáveis .env: FILE_MANAGER_DISK, FILE_MANAGER_ROOT, FILE_MANAGER_TRASH,
FILE_MANAGER_TRASH_DAYS, FILE_MANAGER_MAX_UPLOAD, FILE_MANAGER_MEDIA_URL,
FILE_MANAGER_ROUTE, FILE_MANAGER_ROUTE_PREFIX, FILE_MANAGER_SEARCH_CACHE,
FILE_MANAGER_AUDIT, FILE_MANAGER_AUDIT_CHANNEL.
Nota sobre a rota de media
A rota de media é pública por omissão (route.media_middleware): quem tiver
o URL vê o ficheiro, sem autenticação. Isso é deliberado — permite embeber
imagens em páginas públicas — mas implica que o URL é a única barreira. O lixo e
os sidecars .meta.json nunca são servidos por esta rota. Para conteúdo que
não deva ser público, proteja a rota acrescentando auth a media_middleware.
É esse o endereço que a ação Copiar URL coloca na área de transferência.
Funcionalidades
- Ações em massa (seleção múltipla + checkboxes): mover, copiar, duplicar, eliminar, restaurar, descarregar.
- Multi-raiz: o
root_resolverpode devolver um array de caminhos; cada raiz aparece na sidebar com a sua árvore. - Ordenar por nome (A–Z/Z–A), data (recentes/antigos) e tamanho (maiores/menores); filtrar por pastas/imagens/vídeos/ocultar pastas.
- Pesquisa recursiva em toda a raiz efetiva (não só na pasta atual).
- Download: individual (
GET media|download/<caminho>) e ZIP de seleção/pastas (POST download-zip). - Media com caminho direto no URL (
…/media/pasta/ficheiro.ext), pública por omissão (configurável viaroute.media_middleware). - Upload por drag & drop direto na dropzone do picker (
POST upload). - Picker (
<x-file-manager::picker />): propsize(small|largedropzone), pré-visualização com lightbox, e envio da duração do vídeo porpostMessageao escolher ({ source:'file-manager', type:'video-duration', path, url, duration }). - Loader enquanto qualquer media carrega (grelha, picker, lightbox).
Correspondência Next.js → Livewire
| Antigo (Next.js/React) | Novo (Livewire v4) |
|---|---|
FileManagerContext.js (estado global React) |
Propriedades públicas + #[Computed] em FileManager.php |
FileManager.jsx (layout) |
resources/views/livewire/file-manager.blade.php |
Files.jsx (grid/lista) |
partials/grid-item.blade.php, partials/list-item.blade.php |
NavItem.jsx (árvore) |
partials/tree-node.blade.php + tree() computed |
ContextMenu.jsx |
partials/context-menu.blade.php (Alpine) |
Modal.jsx |
partials/modal.blade.php (Alpine) |
ShowFile.jsx (lightbox) |
partials/lightbox.blade.php |
Filters.jsx, BreadCrumbs.js |
toolbar na view principal + breadcrumbs() |
AddFile.jsx (FAB + upload) |
FAB na view + wire:model="uploads" + updatedUploads() |
api/files, api/files/tree |
FileManagerService::listing() / tree() |
api/folders, api/rename, api/move |
createFolder() / rename() / move() |
api/delete, api/delete/cleanup |
trash() + file-manager:prune-trash |
api/upload, image/route.js |
upload() + MediaController (rota file-manager.media) |
verify-token, TokenListener.jsx (JWT) |
eliminado — usa auth/sessão do Laravel |
iframe + postMessage(SELECTED_FILE) |
<x-file-manager::picker> + evento file-manager-selected |
Melhorias face à versão anterior
- Sem serviço externo: deixa de ser preciso
FILE_MANAGER_URL, proxy HTTP, JWT RS256 e chaves.pem. - Segurança: auth nativa do Laravel +
PathGuardcontra path traversal. - Retenção do lixo correta: era 60s no código (texto dizia 30 dias); agora é configurável (30 por omissão).
- Restaurar itens do lixo (não existia).
- Pesquisa funcional (não existia).
- Multi-disco via Filesystem (S3-ready), em vez de
fsdireto nopublic/. - Navegação preservada ao filtrar por imagens/vídeos (pastas continuam visíveis).
- Limpeza do lixo por comando agendado em vez de polling no cliente.
Integração no Backoffice Proside: ver docs/BACKOFFICE-INTEGRATION.md.