From 82bc9e617bfa1c9f28f965e734f7ae0012ed2ac6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 11 Jul 2020 13:38:49 -0700 Subject: [PATCH] Add support for compressing items in the file manager --- .../Api/Client/Servers/FileController.php | 51 +++++++++++++------ .../Servers/Files/CompressFilesRequest.php | 31 +++++++++++ .../Wings/DaemonFileRepository.php | 24 +++++++++ .../scripts/api/server/files/compressFiles.ts | 12 +++++ .../scripts/api/server/files/loadDirectory.ts | 15 +----- resources/scripts/api/transformers.ts | 15 ++++++ .../server/files/FileDropdownMenu.tsx | 21 ++++++-- routes/api-client.php | 1 + 8 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php create mode 100644 resources/scripts/api/server/files/compressFiles.ts diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 55ebde5f2..470adc2ce 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; use Illuminate\Http\Response; use Pterodactyl\Models\Server; +use Illuminate\Http\JsonResponse; use GuzzleHttp\Exception\TransferException; use Pterodactyl\Services\Nodes\NodeJWTService; use Illuminate\Contracts\Routing\ResponseFactory; @@ -17,6 +18,7 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest; @@ -90,7 +92,7 @@ class FileController extends ClientApiController */ public function getFileContents(GetFileContentsRequest $request, Server $server): Response { - return Response::create( + return new Response( $this->fileRepository->setServer($server)->getContent( $request->get('file'), config('pterodactyl.files.max_edit_size') ), @@ -136,16 +138,16 @@ class FileController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function writeFileContents(WriteFileContentRequest $request, Server $server): Response + public function writeFileContents(WriteFileContentRequest $request, Server $server): JsonResponse { $this->fileRepository->setServer($server)->putContent( $request->get('file'), $request->getContent() ); - return Response::create('', Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -153,15 +155,15 @@ class FileController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function createFolder(CreateFolderRequest $request, Server $server): Response + public function createFolder(CreateFolderRequest $request, Server $server): JsonResponse { $this->fileRepository ->setServer($server) ->createDirectory($request->input('name'), $request->input('root', '/')); - return Response::create('', Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -169,15 +171,15 @@ class FileController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function renameFile(RenameFileRequest $request, Server $server): Response + public function renameFile(RenameFileRequest $request, Server $server): JsonResponse { $this->fileRepository ->setServer($server) ->renameFile($request->input('rename_from'), $request->input('rename_to')); - return Response::create('', Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -185,15 +187,32 @@ class FileController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function copyFile(CopyFileRequest $request, Server $server): Response + public function copyFile(CopyFileRequest $request, Server $server): JsonResponse { $this->fileRepository ->setServer($server) ->copyFile($request->input('location')); - return Response::create('', Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function compressFiles(CompressFilesRequest $request, Server $server): array + { + $file = $this->fileRepository->setServer($server) + ->compressFiles( + $request->input('root'), $request->input('files') + ); + + return $this->fractal->item($file) + ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->toArray(); } /** @@ -201,14 +220,14 @@ class FileController extends ClientApiController * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteFileRequest $request, Server $server): Response + public function delete(DeleteFileRequest $request, Server $server): JsonResponse { $this->fileRepository ->setServer($server) ->deleteFile($request->input('location')); - return Response::create('', Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php new file mode 100644 index 000000000..bd574cf5b --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php @@ -0,0 +1,31 @@ + 'sometimes|nullable|string', + 'files' => 'required|array', + 'files.*' => 'string', + ]; + } +} diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php index c32d9dd20..e69af982f 100644 --- a/app/Repositories/Wings/DaemonFileRepository.php +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -167,4 +167,28 @@ class DaemonFileRepository extends DaemonRepository ] ); } + + /** + * Compress the given files or folders in the given root. + * + * @param string|null $root + * @param array $files + * @return array + */ + public function compressFiles(?string $root, array $files): array + { + Assert::isInstanceOf($this->server, Server::class); + + $response = $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/compress', $this->server->uuid), + [ + 'json' => [ + 'root' => $root ?? '/', + 'files' => $files, + ], + ] + ); + + return json_decode($response->getBody(), true); + } } diff --git a/resources/scripts/api/server/files/compressFiles.ts b/resources/scripts/api/server/files/compressFiles.ts new file mode 100644 index 000000000..0554c7fd9 --- /dev/null +++ b/resources/scripts/api/server/files/compressFiles.ts @@ -0,0 +1,12 @@ +import { FileObject } from '@/api/server/files/loadDirectory'; +import http from '@/api/http'; +import { rawDataToFileObject } from '@/api/transformers'; + +export default async (uuid: string, directory: string, files: string[]): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/files/compress`, { root: directory, files }, { + timeout: 300000, + timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear when completed.', + }); + + return rawDataToFileObject(data); +}; diff --git a/resources/scripts/api/server/files/loadDirectory.ts b/resources/scripts/api/server/files/loadDirectory.ts index 720486dd3..85f290689 100644 --- a/resources/scripts/api/server/files/loadDirectory.ts +++ b/resources/scripts/api/server/files/loadDirectory.ts @@ -1,5 +1,5 @@ import http from '@/api/http'; -import v4 from 'uuid/v4'; +import { rawDataToFileObject } from '@/api/transformers'; export interface FileObject { uuid: string; @@ -19,16 +19,5 @@ export default async (uuid: string, directory?: string): Promise = params: { directory }, }); - return (data.data || []).map((item: any): FileObject => ({ - uuid: v4(), - name: item.attributes.name, - mode: item.attributes.mode, - size: Number(item.attributes.size), - isFile: item.attributes.is_file, - isSymlink: item.attributes.is_symlink, - isEditable: item.attributes.is_editable, - mimetype: item.attributes.mimetype, - createdAt: new Date(item.attributes.created_at), - modifiedAt: new Date(item.attributes.modified_at), - })); + return (data.data || []).map(rawDataToFileObject); }; diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index eb54b62fe..bd59a7e84 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -1,5 +1,7 @@ import { Allocation } from '@/api/server/getServer'; import { FractalResponseData } from '@/api/http'; +import { FileObject } from '@/api/server/files/loadDirectory'; +import v4 from 'uuid/v4'; export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({ id: data.attributes.id, @@ -9,3 +11,16 @@ export const rawDataToServerAllocation = (data: FractalResponseData): Allocation notes: data.attributes.notes, isDefault: data.attributes.is_default, }); + +export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ + uuid: v4(), + name: data.attributes.name, + mode: data.attributes.mode, + size: Number(data.attributes.size), + isFile: data.attributes.is_file, + isSymlink: data.attributes.is_symlink, + isEditable: data.attributes.is_editable, + mimetype: data.attributes.mimetype, + createdAt: new Date(data.attributes.created_at), + modifiedAt: new Date(data.attributes.modified_at), +}); diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index c03e5396e..5d4e14fdc 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCopy, faEllipsisH, + faFileArchive, faFileDownload, faLevelUpAlt, faPencilAlt, @@ -25,6 +26,7 @@ import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import DropdownMenu from '@/components/elements/DropdownMenu'; import styled from 'styled-components/macro'; import useEventListener from '@/plugins/useEventListener'; +import compressFiles from '@/api/server/files/compressFiles'; type ModalType = 'rename' | 'move'; @@ -81,10 +83,8 @@ export default ({ file }: { file: FileObject }) => { copyFile(uuid, join(directory, file.name)) .then(() => mutate()) - .catch(error => { - setShowSpinner(false); - clearAndAddHttpError({ key: 'files', error }); - }); + .catch(error => clearAndAddHttpError({ key: 'files', error })) + .then(() => setShowSpinner(false)); }; const doDownload = () => { @@ -100,6 +100,16 @@ export default ({ file }: { file: FileObject }) => { .then(() => setShowSpinner(false)); }; + const doArchive = () => { + setShowSpinner(true); + clearFlashes('files'); + + compressFiles(uuid, directory, [ file.name ]) + .then(() => mutate()) + .catch(error => clearAndAddHttpError({ key: 'files', error })) + .then(() => setShowSpinner(false)); + }; + return ( { } + + + diff --git a/routes/api-client.php b/routes/api-client.php index 31d6d104e..f9d84545a 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -59,6 +59,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::put('/rename', 'Servers\FileController@renameFile'); Route::post('/copy', 'Servers\FileController@copyFile'); Route::post('/write', 'Servers\FileController@writeFileContents'); + Route::post('/compress', 'Servers\FileController@compressFiles'); Route::post('/delete', 'Servers\FileController@delete'); Route::post('/create-folder', 'Servers\FileController@createFolder'); });