Add support for compressing items in the file manager
This commit is contained in:
parent
cb9eb918b2
commit
82bc9e617b
|
@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
use GuzzleHttp\Exception\TransferException;
|
||||||
use Pterodactyl\Services\Nodes\NodeJWTService;
|
use Pterodactyl\Services\Nodes\NodeJWTService;
|
||||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
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\DeleteFileRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest;
|
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\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\GetFileContentsRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
|
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
|
public function getFileContents(GetFileContentsRequest $request, Server $server): Response
|
||||||
{
|
{
|
||||||
return Response::create(
|
return new Response(
|
||||||
$this->fileRepository->setServer($server)->getContent(
|
$this->fileRepository->setServer($server)->getContent(
|
||||||
$request->get('file'), config('pterodactyl.files.max_edit_size')
|
$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\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @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(
|
$this->fileRepository->setServer($server)->putContent(
|
||||||
$request->get('file'),
|
$request->get('file'),
|
||||||
$request->getContent()
|
$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\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @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
|
$this->fileRepository
|
||||||
->setServer($server)
|
->setServer($server)
|
||||||
->createDirectory($request->input('name'), $request->input('root', '/'));
|
->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\Http\Requests\Api\Client\Servers\Files\RenameFileRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @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
|
$this->fileRepository
|
||||||
->setServer($server)
|
->setServer($server)
|
||||||
->renameFile($request->input('rename_from'), $request->input('rename_to'));
|
->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\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @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
|
$this->fileRepository
|
||||||
->setServer($server)
|
->setServer($server)
|
||||||
->copyFile($request->input('location'));
|
->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\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @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
|
$this->fileRepository
|
||||||
->setServer($server)
|
->setServer($server)
|
||||||
->deleteFile($request->input('location'));
|
->deleteFile($request->input('location'));
|
||||||
|
|
||||||
return Response::create('', Response::HTTP_NO_CONTENT);
|
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Permission;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||||
|
|
||||||
|
class CompressFilesRequest extends ClientApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks that the authenticated user is allowed to create archives for this server.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function permission(): string
|
||||||
|
{
|
||||||
|
return Permission::ACTION_FILE_ARCHIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'root' => 'sometimes|nullable|string',
|
||||||
|
'files' => 'required|array',
|
||||||
|
'files.*' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<FileObject> => {
|
||||||
|
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);
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import v4 from 'uuid/v4';
|
import { rawDataToFileObject } from '@/api/transformers';
|
||||||
|
|
||||||
export interface FileObject {
|
export interface FileObject {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
@ -19,16 +19,5 @@ export default async (uuid: string, directory?: string): Promise<FileObject[]> =
|
||||||
params: { directory },
|
params: { directory },
|
||||||
});
|
});
|
||||||
|
|
||||||
return (data.data || []).map((item: any): FileObject => ({
|
return (data.data || []).map(rawDataToFileObject);
|
||||||
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),
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Allocation } from '@/api/server/getServer';
|
import { Allocation } from '@/api/server/getServer';
|
||||||
import { FractalResponseData } from '@/api/http';
|
import { FractalResponseData } from '@/api/http';
|
||||||
|
import { FileObject } from '@/api/server/files/loadDirectory';
|
||||||
|
import v4 from 'uuid/v4';
|
||||||
|
|
||||||
export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({
|
export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({
|
||||||
id: data.attributes.id,
|
id: data.attributes.id,
|
||||||
|
@ -9,3 +11,16 @@ export const rawDataToServerAllocation = (data: FractalResponseData): Allocation
|
||||||
notes: data.attributes.notes,
|
notes: data.attributes.notes,
|
||||||
isDefault: data.attributes.is_default,
|
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),
|
||||||
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import {
|
import {
|
||||||
faCopy,
|
faCopy,
|
||||||
faEllipsisH,
|
faEllipsisH,
|
||||||
|
faFileArchive,
|
||||||
faFileDownload,
|
faFileDownload,
|
||||||
faLevelUpAlt,
|
faLevelUpAlt,
|
||||||
faPencilAlt,
|
faPencilAlt,
|
||||||
|
@ -25,6 +26,7 @@ import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||||
import DropdownMenu from '@/components/elements/DropdownMenu';
|
import DropdownMenu from '@/components/elements/DropdownMenu';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import useEventListener from '@/plugins/useEventListener';
|
import useEventListener from '@/plugins/useEventListener';
|
||||||
|
import compressFiles from '@/api/server/files/compressFiles';
|
||||||
|
|
||||||
type ModalType = 'rename' | 'move';
|
type ModalType = 'rename' | 'move';
|
||||||
|
|
||||||
|
@ -81,10 +83,8 @@ export default ({ file }: { file: FileObject }) => {
|
||||||
|
|
||||||
copyFile(uuid, join(directory, file.name))
|
copyFile(uuid, join(directory, file.name))
|
||||||
.then(() => mutate())
|
.then(() => mutate())
|
||||||
.catch(error => {
|
.catch(error => clearAndAddHttpError({ key: 'files', error }))
|
||||||
setShowSpinner(false);
|
.then(() => setShowSpinner(false));
|
||||||
clearAndAddHttpError({ key: 'files', error });
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const doDownload = () => {
|
const doDownload = () => {
|
||||||
|
@ -100,6 +100,16 @@ export default ({ file }: { file: FileObject }) => {
|
||||||
.then(() => setShowSpinner(false));
|
.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 (
|
return (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
ref={onClickRef}
|
ref={onClickRef}
|
||||||
|
@ -125,6 +135,9 @@ export default ({ file }: { file: FileObject }) => {
|
||||||
<Row onClick={doCopy} icon={faCopy} title={'Copy'}/>
|
<Row onClick={doCopy} icon={faCopy} title={'Copy'}/>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
|
<Can action={'file.archive'}>
|
||||||
|
<Row onClick={doArchive} icon={faFileArchive} title={'Archive'}/>
|
||||||
|
</Can>
|
||||||
<Row onClick={doDownload} icon={faFileDownload} title={'Download'}/>
|
<Row onClick={doDownload} icon={faFileDownload} title={'Download'}/>
|
||||||
<Can action={'file.delete'}>
|
<Can action={'file.delete'}>
|
||||||
<Row onClick={doDeletion} icon={faTrashAlt} title={'Delete'} $danger/>
|
<Row onClick={doDeletion} icon={faTrashAlt} title={'Delete'} $danger/>
|
||||||
|
|
|
@ -59,6 +59,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
|
||||||
Route::put('/rename', 'Servers\FileController@renameFile');
|
Route::put('/rename', 'Servers\FileController@renameFile');
|
||||||
Route::post('/copy', 'Servers\FileController@copyFile');
|
Route::post('/copy', 'Servers\FileController@copyFile');
|
||||||
Route::post('/write', 'Servers\FileController@writeFileContents');
|
Route::post('/write', 'Servers\FileController@writeFileContents');
|
||||||
|
Route::post('/compress', 'Servers\FileController@compressFiles');
|
||||||
Route::post('/delete', 'Servers\FileController@delete');
|
Route::post('/delete', 'Servers\FileController@delete');
|
||||||
Route::post('/create-folder', 'Servers\FileController@createFolder');
|
Route::post('/create-folder', 'Servers\FileController@createFolder');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue