diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php new file mode 100644 index 000000000..6ceae16e7 --- /dev/null +++ b/app/Http/Controllers/Admin/LocationController.php @@ -0,0 +1,119 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use Log; +use Alert; +use Illuminate\Http\Request; +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\LocationRepository; +use Pterodactyl\Exceptions\DisplayValidationException; + +class LocationController extends Controller +{ + /** + * Return the location overview page. + * + * @param Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request) + { + return view('admin.locations.index', [ + 'locations' => Location::withCount('nodes', 'servers')->get(), + ]); + } + + /** + * Return the location view page. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) + { + return view('admin.locations.view', ['location' => Location::with('nodes.servers')->findOrFail($id)]); + } + + /** + * Handle request to create new location. + * + * @param Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function create(Request $request) + { + $repo = new LocationRepository; + + try { + $location = $repo->create($request->intersect(['short', 'long'])); + Alert::success('Location was created successfully.')->flash(); + + return redirect()->route('admin.locations.view', $location->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage())); + } catch (\Exception $ex) { + Log::error($ex); + Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.locations'); + } + + /** + * Handle request to update or delete location. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function update(Request $request, $id) + { + $repo = new LocationRepository; + + try { + if ($request->input('action') !== 'delete') { + $location = $repo->update($id, $request->intersect(['short', 'long'])); + Alert::success('Location was updated successfully.')->flash(); + } else { + $repo->delete($id); + + return redirect()->route('admin.locations'); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.locations.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.locations.view', $id); + } +} diff --git a/app/Http/Controllers/Admin/LocationsController.php b/app/Http/Controllers/Admin/LocationsController.php deleted file mode 100644 index 94c9ebf59..000000000 --- a/app/Http/Controllers/Admin/LocationsController.php +++ /dev/null @@ -1,100 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Alert; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\LocationRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class LocationsController extends Controller -{ - public function __construct() - { - // - } - - public function getIndex(Request $request) - { - return view('admin.locations.index', [ - 'locations' => Models\Location::withCount('nodes', 'servers')->paginate(20), - ]); - } - - public function deleteLocation(Request $request, $id) - { - $location = Models\Location::withCount('nodes')->findOrFail($id); - - if ($location->nodes_count > 0) { - return response()->json([ - 'error' => 'You cannot remove a location that is currently assigned to a node.', - ], 422); - } - - $location->delete(); - - return response('', 204); - } - - public function patchLocation(Request $request, $id) - { - try { - $location = new LocationRepository; - $location->edit($id, $request->only(['long', 'short'])); - - return response('', 204); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 20 characters with no spaces or special characters.', - ], 422); - } catch (\Exception $ex) { - // This gets caught and processed into JSON anyways. - throw $ex; - } - } - - public function postLocation(Request $request) - { - try { - $location = new LocationRepository; - $location->create($request->only(['long', 'short'])); - Alert::success('New location successfully added.')->flash(); - - return redirect()->route('admin.locations'); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add this location. Please try again.')->flash(); - } - - return redirect()->route('admin.locations')->withInput(); - } -} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index f8cbc17fa..8649e789f 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -43,6 +43,29 @@ class AdminRoutes 'uses' => 'Admin\BaseController@getIndex', ]); + $router->group([ + 'prefix' => 'admin/locations', + 'middleware' => [ + 'auth', + 'admin', + 'csrf', + ], + ], function () use ($router) { + $router->get('/', [ + 'as' => 'admin.locations', + 'uses' => 'Admin\LocationController@index', + ]); + + $router->post('/', 'Admin\LocationController@create'); + + $router->get('/view/{id}', [ + 'as' => 'admin.locations.view', + 'uses' => 'Admin\LocationController@view', + ]); + + $router->post('/view/{id}', 'Admin\LocationController@update'); + }); + $router->group([ 'prefix' => 'admin/settings', 'middleware' => [ @@ -321,60 +344,6 @@ class AdminRoutes ]); }); - // Location Routes - $router->group([ - 'prefix' => 'admin/locations', - 'middleware' => [ - 'auth', - 'admin', - 'csrf', - ], - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.locations', - 'uses' => 'Admin\LocationsController@getIndex', - ]); - $router->delete('/{id}', [ - 'uses' => 'Admin\LocationsController@deleteLocation', - ]); - $router->patch('/{id}', [ - 'uses' => 'Admin\LocationsController@patchLocation', - ]); - $router->post('/', [ - 'uses' => 'Admin\LocationsController@postLocation', - ]); - }); - - // Database Routes - $router->group([ - 'prefix' => 'admin/databases', - 'middleware' => [ - 'auth', - 'admin', - 'csrf', - ], - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.databases', - 'uses' => 'Admin\DatabaseController@getIndex', - ]); - $router->get('/new', [ - 'as' => 'admin.databases.new', - 'uses' => 'Admin\DatabaseController@getNew', - ]); - $router->post('/new', [ - 'uses' => 'Admin\DatabaseController@postNew', - ]); - $router->delete('/delete/{id}', [ - 'as' => 'admin.databases.delete', - 'uses' => 'Admin\DatabaseController@deleteDatabase', - ]); - $router->delete('/delete-server/{id}', [ - 'as' => 'admin.databases.delete-server', - 'uses' => 'Admin\DatabaseController@deleteServer', - ]); - }); - // Service Routes $router->group([ 'prefix' => 'admin/services', diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/LocationRepository.php index 1b6f50613..d3efb5d25 100644 --- a/app/Repositories/LocationRepository.php +++ b/app/Repositories/LocationRepository.php @@ -25,16 +25,12 @@ namespace Pterodactyl\Repositories; use Validator; -use Pterodactyl\Models; +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; class LocationRepository { - public function __construct() - { - // - } - /** * Creates a new location on the system. * @@ -45,48 +41,63 @@ class LocationRepository public function create(array $data) { $validator = Validator::make($data, [ - 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short', - 'long' => 'required|string|min:1|max:255', + 'short' => 'required|string|between:1,60|unique:locations,short', + 'long' => 'required|string|between:1,255', ]); - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - $location = Models\Location::create([ + return Location::create([ 'long' => $data['long'], 'short' => $data['short'], ]); + } + + /** + * Modifies a location. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $location = Location::findOrFail($id); + + $validator = Validator::make($data, [ + 'short' => 'sometimes|required|string|between:1,60|unique:locations,short,' . $location->id, + 'long' => 'sometimes|required|string|between:1,255', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + $location->fill($data)->save(); return $location; } /** - * Modifies a location based on the fields passed in $data. - * @param int $id - * @param array $data - * @throws Pterodactyl\Exceptions\DisplayValidationException - * @return bool + * Deletes a location from the system. + * + * @param int $id + * @return void + * + * @throws Pterodactyl\Exceptions\DisplayException */ - public function edit($id, array $data) + public function delete($id) { - $location = Models\Location::findOrFail($id); + $location = Location::withCount('nodes')->findOrFail($id); - $validator = Validator::make($data, [ - 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short,' . $location->id, - 'long' => 'required|string|min:1|max:255', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + if ($location->nodes_count > 0) { + throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); } - $location->fill($data); - - return $location->save(); + $location->delete(); } } diff --git a/resources/themes/pterodactyl/admin/locations/index.blade.php b/resources/themes/pterodactyl/admin/locations/index.blade.php new file mode 100644 index 000000000..e2b8578a0 --- /dev/null +++ b/resources/themes/pterodactyl/admin/locations/index.blade.php @@ -0,0 +1,100 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Locations +@endsection + +@section('content-header') +

LocationsAll locations that nodes can be assigned to for easier categorization.

+ +@endsection + +@section('content') +
+
+
+
+

Location List

+
+
+ + + + + + + + + + @foreach ($locations as $location) + + + + + + + + @endforeach + +
IDShort CodeDescriptionNodesServers
{{ $location->id }}{{ $location->short }}{{ $location->long }}{{ $location->nodes_count }}{{ $location->servers_count }}
+
+ +
+
+
+ +@endsection diff --git a/resources/themes/pterodactyl/admin/locations/view.blade.php b/resources/themes/pterodactyl/admin/locations/view.blade.php new file mode 100644 index 000000000..41490f4b9 --- /dev/null +++ b/resources/themes/pterodactyl/admin/locations/view.blade.php @@ -0,0 +1,87 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Locations → View → {{ $location->short }} +@endsection + +@section('content-header') +

{{ $location->short }}{{ str_limit($location->long, 75) }}

+ +@endsection + +@section('content') +
+
+
+
+

Location Details

+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+

Nodes

+
+
+ + + + + + + + @foreach($location->nodes as $node) + + + + + + + @endforeach +
IDNameFQDNServers
{{ $node->id }}{{ $node->name }}{{ $node->fqdn }}{{ $node->servers->count() }}
+
+
+
+
+@endsection diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php index a6c735c60..85ac74160 100644 --- a/resources/themes/pterodactyl/layouts/admin.blade.php +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -91,9 +91,14 @@
  • MANAGEMENT
  • -
  • +
  • - Servers + Databases + +
  • +
  • + + Locations
  • @@ -101,6 +106,11 @@ Nodes
  • +
  • + + Servers + +
  • Users