diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 689c26426..eac0cd6dc 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -18,6 +18,7 @@ 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\DownloadFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest; class FileController extends ClientApiController { @@ -83,6 +84,22 @@ class FileController extends ClientApiController ); } + /** + * Writes the contents of the specified file to the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request + * @return \Illuminate\Http\Response + */ + public function writeFileContents(WriteFileContentRequest $request): Response + { + $this->fileRepository->setServer($request->getModel(Server::class))->putContent( + $request->get('file'), + $request->getContent() + ); + + return Response::create('', Response::HTTP_NO_CONTENT); + } + /** * Creates a new folder on the server. * diff --git a/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php b/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php new file mode 100644 index 000000000..31404938a --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php @@ -0,0 +1,35 @@ + 'required|string', + ]; + } +} diff --git a/resources/assets/scripts/api/http.ts b/resources/assets/scripts/api/http.ts index 6c3148074..6648a63db 100644 --- a/resources/assets/scripts/api/http.ts +++ b/resources/assets/scripts/api/http.ts @@ -1,4 +1,4 @@ -import axios, {AxiosInstance} from 'axios'; +import axios, {AxiosError, AxiosInstance} from 'axios'; import {ServerApplicationCredentials} from "@/store/types"; // 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 return http; } + +/** + * Converts an error into a human readable response. Mostly just a generic helper to + * make sure we display the message from the server back to the user if we can. + */ +export function httpErrorToHuman(error: any): string { + if (error.response && error.response.data) { + const { data } = error.response; + if (data.errors && data.errors[0] && data.errors[0].detail) { + return data.errors[0].detail; + } + } + + return error.message; +} diff --git a/resources/assets/scripts/api/server/files/writeFileContents.ts b/resources/assets/scripts/api/server/files/writeFileContents.ts new file mode 100644 index 000000000..065c4a8db --- /dev/null +++ b/resources/assets/scripts/api/server/files/writeFileContents.ts @@ -0,0 +1,14 @@ +import http from "@/api/http"; + +export default (server: string, file: string, content: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${server}/files/write`, content, { + params: { file }, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + }, + }) + .then(() => resolve()) + .catch(reject); + }); +} diff --git a/resources/assets/scripts/components/server/components/filemanager/modals/EditFileModal.vue b/resources/assets/scripts/components/server/components/filemanager/modals/EditFileModal.vue index 3b402508f..c4d8a98f0 100644 --- a/resources/assets/scripts/components/server/components/filemanager/modals/EditFileModal.vue +++ b/resources/assets/scripts/components/server/components/filemanager/modals/EditFileModal.vue @@ -19,7 +19,7 @@ - @@ -39,6 +39,8 @@ import {DirectoryContentObject} from "@/api/server/types"; import getFileContents from '@/api/server/files/getFileContents'; import SpinnerModal from "@/components/core/SpinnerModal.vue"; + import writeFileContents from '@/api/server/files/writeFileContents'; + import {httpErrorToHuman} from '@/api/http'; interface Data { file?: DirectoryContentObject, @@ -120,7 +122,16 @@ methods: { submit: function () { + this.isLoading = true; + const content = this.editor!.getValue(); + writeFileContents(this.serverUuid!, join(this.fm!.currentDirectory, this.file!.name), content) + .then(() => this.error = null) + .catch(error => { + console.log(error); + this.error = httpErrorToHuman(error); + }) + .then(() => this.isLoading = false); }, loadFileContent: function (): Promise { diff --git a/routes/api-client.php b/routes/api-client.php index 155280ad1..7bf174ef1 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -46,6 +46,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::get('/contents', 'Servers\FileController@getFileContents')->name('api.client.servers.files.contents'); Route::put('/rename', 'Servers\FileController@renameFile')->name('api.client.servers.files.rename'); Route::post('/copy', 'Servers\FileController@copyFile')->name('api.client.servers.files.copy'); + Route::post('/write', 'Servers\FileController@writeFileContents')->name('api.client.servers.files.write'); Route::post('/delete', 'Servers\FileController@delete')->name('api.client.servers.files.delete'); Route::post('/create-folder', 'Servers\FileController@createFolder')->name('api.client.servers.files.create-folder');