Skip to content

Commit 82bc9e6

Browse files
committed
Add support for compressing items in the file manager
1 parent cb9eb91 commit 82bc9e6

File tree

8 files changed

+137
-33
lines changed

8 files changed

+137
-33
lines changed

app/Http/Controllers/Api/Client/Servers/FileController.php

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Carbon\CarbonImmutable;
66
use Illuminate\Http\Response;
77
use Pterodactyl\Models\Server;
8+
use Illuminate\Http\JsonResponse;
89
use GuzzleHttp\Exception\TransferException;
910
use Pterodactyl\Services\Nodes\NodeJWTService;
1011
use Illuminate\Contracts\Routing\ResponseFactory;
@@ -17,6 +18,7 @@
1718
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest;
1819
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest;
1920
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest;
21+
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest;
2022
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest;
2123
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
2224

@@ -90,7 +92,7 @@ public function listDirectory(ListFilesRequest $request, Server $server): array
9092
*/
9193
public function getFileContents(GetFileContentsRequest $request, Server $server): Response
9294
{
93-
return Response::create(
95+
return new Response(
9496
$this->fileRepository->setServer($server)->getContent(
9597
$request->get('file'), config('pterodactyl.files.max_edit_size')
9698
),
@@ -136,79 +138,96 @@ public function download(GetFileContentsRequest $request, Server $server)
136138
*
137139
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request
138140
* @param \Pterodactyl\Models\Server $server
139-
* @return \Illuminate\Http\Response
141+
* @return \Illuminate\Http\JsonResponse
140142
*/
141-
public function writeFileContents(WriteFileContentRequest $request, Server $server): Response
143+
public function writeFileContents(WriteFileContentRequest $request, Server $server): JsonResponse
142144
{
143145
$this->fileRepository->setServer($server)->putContent(
144146
$request->get('file'),
145147
$request->getContent()
146148
);
147149

148-
return Response::create('', Response::HTTP_NO_CONTENT);
150+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
149151
}
150152

151153
/**
152154
* Creates a new folder on the server.
153155
*
154156
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest $request
155157
* @param \Pterodactyl\Models\Server $server
156-
* @return \Illuminate\Http\Response
158+
* @return \Illuminate\Http\JsonResponse
157159
*/
158-
public function createFolder(CreateFolderRequest $request, Server $server): Response
160+
public function createFolder(CreateFolderRequest $request, Server $server): JsonResponse
159161
{
160162
$this->fileRepository
161163
->setServer($server)
162164
->createDirectory($request->input('name'), $request->input('root', '/'));
163165

164-
return Response::create('', Response::HTTP_NO_CONTENT);
166+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
165167
}
166168

167169
/**
168170
* Renames a file on the remote machine.
169171
*
170172
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest $request
171173
* @param \Pterodactyl\Models\Server $server
172-
* @return \Illuminate\Http\Response
174+
* @return \Illuminate\Http\JsonResponse
173175
*/
174-
public function renameFile(RenameFileRequest $request, Server $server): Response
176+
public function renameFile(RenameFileRequest $request, Server $server): JsonResponse
175177
{
176178
$this->fileRepository
177179
->setServer($server)
178180
->renameFile($request->input('rename_from'), $request->input('rename_to'));
179181

180-
return Response::create('', Response::HTTP_NO_CONTENT);
182+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
181183
}
182184

183185
/**
184186
* Copies a file on the server.
185187
*
186188
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request
187189
* @param \Pterodactyl\Models\Server $server
188-
* @return \Illuminate\Http\Response
190+
* @return \Illuminate\Http\JsonResponse
189191
*/
190-
public function copyFile(CopyFileRequest $request, Server $server): Response
192+
public function copyFile(CopyFileRequest $request, Server $server): JsonResponse
191193
{
192194
$this->fileRepository
193195
->setServer($server)
194196
->copyFile($request->input('location'));
195197

196-
return Response::create('', Response::HTTP_NO_CONTENT);
198+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
199+
}
200+
201+
/**
202+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest $request
203+
* @param \Pterodactyl\Models\Server $server
204+
* @return array
205+
*/
206+
public function compressFiles(CompressFilesRequest $request, Server $server): array
207+
{
208+
$file = $this->fileRepository->setServer($server)
209+
->compressFiles(
210+
$request->input('root'), $request->input('files')
211+
);
212+
213+
return $this->fractal->item($file)
214+
->transformWith($this->getTransformer(FileObjectTransformer::class))
215+
->toArray();
197216
}
198217

199218
/**
200219
* Deletes a file or folder from the server.
201220
*
202221
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request
203222
* @param \Pterodactyl\Models\Server $server
204-
* @return \Illuminate\Http\Response
223+
* @return \Illuminate\Http\JsonResponse
205224
*/
206-
public function delete(DeleteFileRequest $request, Server $server): Response
225+
public function delete(DeleteFileRequest $request, Server $server): JsonResponse
207226
{
208227
$this->fileRepository
209228
->setServer($server)
210229
->deleteFile($request->input('location'));
211230

212-
return Response::create('', Response::HTTP_NO_CONTENT);
231+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
213232
}
214233
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
4+
5+
use Pterodactyl\Models\Permission;
6+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
7+
8+
class CompressFilesRequest extends ClientApiRequest
9+
{
10+
/**
11+
* Checks that the authenticated user is allowed to create archives for this server.
12+
*
13+
* @return string
14+
*/
15+
public function permission(): string
16+
{
17+
return Permission::ACTION_FILE_ARCHIVE;
18+
}
19+
20+
/**
21+
* @return array
22+
*/
23+
public function rules(): array
24+
{
25+
return [
26+
'root' => 'sometimes|nullable|string',
27+
'files' => 'required|array',
28+
'files.*' => 'string',
29+
];
30+
}
31+
}

app/Repositories/Wings/DaemonFileRepository.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,28 @@ public function deleteFile(string $location): ResponseInterface
167167
]
168168
);
169169
}
170+
171+
/**
172+
* Compress the given files or folders in the given root.
173+
*
174+
* @param string|null $root
175+
* @param array $files
176+
* @return array
177+
*/
178+
public function compressFiles(?string $root, array $files): array
179+
{
180+
Assert::isInstanceOf($this->server, Server::class);
181+
182+
$response = $this->getHttpClient()->post(
183+
sprintf('/api/servers/%s/files/compress', $this->server->uuid),
184+
[
185+
'json' => [
186+
'root' => $root ?? '/',
187+
'files' => $files,
188+
],
189+
]
190+
);
191+
192+
return json_decode($response->getBody(), true);
193+
}
170194
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { FileObject } from '@/api/server/files/loadDirectory';
2+
import http from '@/api/http';
3+
import { rawDataToFileObject } from '@/api/transformers';
4+
5+
export default async (uuid: string, directory: string, files: string[]): Promise<FileObject> => {
6+
const { data } = await http.post(`/api/client/servers/${uuid}/files/compress`, { root: directory, files }, {
7+
timeout: 300000,
8+
timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear when completed.',
9+
});
10+
11+
return rawDataToFileObject(data);
12+
};
Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import http from '@/api/http';
2-
import v4 from 'uuid/v4';
2+
import { rawDataToFileObject } from '@/api/transformers';
33

44
export interface FileObject {
55
uuid: string;
@@ -19,16 +19,5 @@ export default async (uuid: string, directory?: string): Promise<FileObject[]> =
1919
params: { directory },
2020
});
2121

22-
return (data.data || []).map((item: any): FileObject => ({
23-
uuid: v4(),
24-
name: item.attributes.name,
25-
mode: item.attributes.mode,
26-
size: Number(item.attributes.size),
27-
isFile: item.attributes.is_file,
28-
isSymlink: item.attributes.is_symlink,
29-
isEditable: item.attributes.is_editable,
30-
mimetype: item.attributes.mimetype,
31-
createdAt: new Date(item.attributes.created_at),
32-
modifiedAt: new Date(item.attributes.modified_at),
33-
}));
22+
return (data.data || []).map(rawDataToFileObject);
3423
};

resources/scripts/api/transformers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Allocation } from '@/api/server/getServer';
22
import { FractalResponseData } from '@/api/http';
3+
import { FileObject } from '@/api/server/files/loadDirectory';
4+
import v4 from 'uuid/v4';
35

46
export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({
57
id: data.attributes.id,
@@ -9,3 +11,16 @@ export const rawDataToServerAllocation = (data: FractalResponseData): Allocation
911
notes: data.attributes.notes,
1012
isDefault: data.attributes.is_default,
1113
});
14+
15+
export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({
16+
uuid: v4(),
17+
name: data.attributes.name,
18+
mode: data.attributes.mode,
19+
size: Number(data.attributes.size),
20+
isFile: data.attributes.is_file,
21+
isSymlink: data.attributes.is_symlink,
22+
isEditable: data.attributes.is_editable,
23+
mimetype: data.attributes.mimetype,
24+
createdAt: new Date(data.attributes.created_at),
25+
modifiedAt: new Date(data.attributes.modified_at),
26+
});

resources/scripts/components/server/files/FileDropdownMenu.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
33
import {
44
faCopy,
55
faEllipsisH,
6+
faFileArchive,
67
faFileDownload,
78
faLevelUpAlt,
89
faPencilAlt,
@@ -25,6 +26,7 @@ import useFileManagerSwr from '@/plugins/useFileManagerSwr';
2526
import DropdownMenu from '@/components/elements/DropdownMenu';
2627
import styled from 'styled-components/macro';
2728
import useEventListener from '@/plugins/useEventListener';
29+
import compressFiles from '@/api/server/files/compressFiles';
2830

2931
type ModalType = 'rename' | 'move';
3032

@@ -81,10 +83,8 @@ export default ({ file }: { file: FileObject }) => {
8183

8284
copyFile(uuid, join(directory, file.name))
8385
.then(() => mutate())
84-
.catch(error => {
85-
setShowSpinner(false);
86-
clearAndAddHttpError({ key: 'files', error });
87-
});
86+
.catch(error => clearAndAddHttpError({ key: 'files', error }))
87+
.then(() => setShowSpinner(false));
8888
};
8989

9090
const doDownload = () => {
@@ -100,6 +100,16 @@ export default ({ file }: { file: FileObject }) => {
100100
.then(() => setShowSpinner(false));
101101
};
102102

103+
const doArchive = () => {
104+
setShowSpinner(true);
105+
clearFlashes('files');
106+
107+
compressFiles(uuid, directory, [ file.name ])
108+
.then(() => mutate())
109+
.catch(error => clearAndAddHttpError({ key: 'files', error }))
110+
.then(() => setShowSpinner(false));
111+
};
112+
103113
return (
104114
<DropdownMenu
105115
ref={onClickRef}
@@ -125,6 +135,9 @@ export default ({ file }: { file: FileObject }) => {
125135
<Row onClick={doCopy} icon={faCopy} title={'Copy'}/>
126136
</Can>
127137
}
138+
<Can action={'file.archive'}>
139+
<Row onClick={doArchive} icon={faFileArchive} title={'Archive'}/>
140+
</Can>
128141
<Row onClick={doDownload} icon={faFileDownload} title={'Download'}/>
129142
<Can action={'file.delete'}>
130143
<Row onClick={doDeletion} icon={faTrashAlt} title={'Delete'} $danger/>

routes/api-client.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
Route::put('/rename', 'Servers\FileController@renameFile');
6060
Route::post('/copy', 'Servers\FileController@copyFile');
6161
Route::post('/write', 'Servers\FileController@writeFileContents');
62+
Route::post('/compress', 'Servers\FileController@compressFiles');
6263
Route::post('/delete', 'Servers\FileController@delete');
6364
Route::post('/create-folder', 'Servers\FileController@createFolder');
6465
});

0 commit comments

Comments
 (0)