Skip to content

Commit bfdc1f7

Browse files
committed
Support saving existing files
1 parent a8f523e commit bfdc1f7

File tree

6 files changed

+96
-3
lines changed

6 files changed

+96
-3
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest;
1919
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest;
2020
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest;
21+
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
2122

2223
class FileController extends ClientApiController
2324
{
@@ -83,6 +84,22 @@ public function getFileContents(GetFileContentsRequest $request): Response
8384
);
8485
}
8586

87+
/**
88+
* Writes the contents of the specified file to the server.
89+
*
90+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request
91+
* @return \Illuminate\Http\Response
92+
*/
93+
public function writeFileContents(WriteFileContentRequest $request): Response
94+
{
95+
$this->fileRepository->setServer($request->getModel(Server::class))->putContent(
96+
$request->get('file'),
97+
$request->getContent()
98+
);
99+
100+
return Response::create('', Response::HTTP_NO_CONTENT);
101+
}
102+
86103
/**
87104
* Creates a new folder on the server.
88105
*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
4+
5+
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
6+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
7+
8+
class WriteFileContentRequest extends ClientApiRequest implements ClientPermissionsRequest
9+
{
10+
/**
11+
* Returns the permissions string indicating which permission should be used to
12+
* validate that the authenticated user has permission to perform this action aganist
13+
* the given resource (server).
14+
*
15+
* @return string
16+
*/
17+
public function permission(): string
18+
{
19+
return 'save-files';
20+
}
21+
22+
/**
23+
* There is no rule here for the file contents since we just use the body content
24+
* on the request to set the file contents. If nothing is passed that is fine since
25+
* it just means we want to set the file to be empty.
26+
*
27+
* @return array
28+
*/
29+
public function rules(): array
30+
{
31+
return [
32+
'file' => 'required|string',
33+
];
34+
}
35+
}

resources/assets/scripts/api/http.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios, {AxiosInstance} from 'axios';
1+
import axios, {AxiosError, AxiosInstance} from 'axios';
22
import {ServerApplicationCredentials} from "@/store/types";
33

44
// This token is set in the bootstrap.js file at the beginning of the request
@@ -38,3 +38,18 @@ export function withCredentials(server: string, credentials: ServerApplicationCr
3838

3939
return http;
4040
}
41+
42+
/**
43+
* Converts an error into a human readable response. Mostly just a generic helper to
44+
* make sure we display the message from the server back to the user if we can.
45+
*/
46+
export function httpErrorToHuman(error: any): string {
47+
if (error.response && error.response.data) {
48+
const { data } = error.response;
49+
if (data.errors && data.errors[0] && data.errors[0].detail) {
50+
return data.errors[0].detail;
51+
}
52+
}
53+
54+
return error.message;
55+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import http from "@/api/http";
2+
3+
export default (server: string, file: string, content: string): Promise<void> => {
4+
return new Promise((resolve, reject) => {
5+
http.post(`/api/client/servers/${server}/files/write`, content, {
6+
params: { file },
7+
headers: {
8+
'Content-Type': 'text/plain; charset=utf-8',
9+
},
10+
})
11+
.then(() => resolve())
12+
.catch(reject);
13+
});
14+
}

resources/assets/scripts/components/server/components/filemanager/modals/EditFileModal.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<button class="btn btn-secondary btn-sm" v-on:click="closeModal">
2020
Cancel
2121
</button>
22-
<button class="ml-2 btn btn-primary btn-sm">
22+
<button class="ml-2 btn btn-primary btn-sm" v-on:click="submit">
2323
Save
2424
</button>
2525
</div>
@@ -39,6 +39,8 @@
3939
import {DirectoryContentObject} from "@/api/server/types";
4040
import getFileContents from '@/api/server/files/getFileContents';
4141
import SpinnerModal from "@/components/core/SpinnerModal.vue";
42+
import writeFileContents from '@/api/server/files/writeFileContents';
43+
import {httpErrorToHuman} from '@/api/http';
4244
4345
interface Data {
4446
file?: DirectoryContentObject,
@@ -120,7 +122,16 @@
120122
121123
methods: {
122124
submit: function () {
123-
125+
this.isLoading = true;
126+
const content = this.editor!.getValue();
127+
128+
writeFileContents(this.serverUuid!, join(this.fm!.currentDirectory, this.file!.name), content)
129+
.then(() => this.error = null)
130+
.catch(error => {
131+
console.log(error);
132+
this.error = httpErrorToHuman(error);
133+
})
134+
.then(() => this.isLoading = false);
124135
},
125136
126137
loadFileContent: function (): Promise<void> {

routes/api-client.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
Route::get('/contents', 'Servers\FileController@getFileContents')->name('api.client.servers.files.contents');
4747
Route::put('/rename', 'Servers\FileController@renameFile')->name('api.client.servers.files.rename');
4848
Route::post('/copy', 'Servers\FileController@copyFile')->name('api.client.servers.files.copy');
49+
Route::post('/write', 'Servers\FileController@writeFileContents')->name('api.client.servers.files.write');
4950
Route::post('/delete', 'Servers\FileController@delete')->name('api.client.servers.files.delete');
5051
Route::post('/create-folder', 'Servers\FileController@createFolder')->name('api.client.servers.files.create-folder');
5152

0 commit comments

Comments
 (0)