1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-10-27 12:22:28 +01:00

Add back locations with new theme

This commit is contained in:
Dane Everitt 2017-03-16 18:09:02 -04:00
parent db072025d4
commit 05d2a6d370
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
7 changed files with 383 additions and 187 deletions

View File

@ -0,0 +1,119 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);
}
}

View File

@ -1,100 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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();
}
}

View File

@ -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',

View File

@ -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();
}
}

View File

@ -0,0 +1,100 @@
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
{{-- 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')
<h1>Locations<small>All locations that nodes can be assigned to for easier categorization.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li class="active">Locations</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Location List</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<th>ID</th>
<th>Short Code</th>
<th>Description</th>
<th class="text-center">Nodes</th>
<th class="text-center">Servers</th>
</tr>
@foreach ($locations as $location)
<tr>
<td><code>{{ $location->id }}</code></td>
<td><a href="{{ route('admin.locations.view', $location->id) }}">{{ $location->short }}</a></td>
<td>{{ $location->long }}</td>
<td class="text-center">{{ $location->nodes_count }}</td>
<td class="text-center">{{ $location->servers_count }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="box-footer">
<button class="btn btn-sm btn-default pull-right" data-toggle="modal" data-target="#newLocationModal">New Location</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="newLocationModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="{{ route('admin.locations') }}" method="POST">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Create Location</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<label for="pShortModal" class="form-label">Short Code</label>
<input type="text" name="short" id="pShortModal" class="form-control" />
<p class="text-muted small">A short identifier used to distinguish this location from others. Must be between 1 and 60 characters, for example, <code>us.nyc.lvl3</code>.</p>
</div>
<div class="col-md-12">
<label for="pLongModal" class="form-label">Description</label>
<textarea name="long" id="pLongModal" class="form-control" rows="4"></textarea>
<p class="text-muted small">A longer description of this location. Must be less than 255 characters.</p>
</div>
</div>
</div>
<div class="modal-footer">
{!! csrf_field() !!}
<button type="button" class="btn btn-default btn-sm pull-left" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success btn-sm">Create</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,87 @@
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
{{-- 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 &rarr; View &rarr; {{ $location->short }}
@endsection
@section('content-header')
<h1>{{ $location->short }}<small>{{ str_limit($location->long, 75) }}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.locations') }}">Locations</a></li>
<li class="active">{{ $location->short }}</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-sm-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Location Details</h3>
</div>
<form action="{{ route('admin.locations.view', $location->id) }}" method="POST">
<div class="box-body">
<div class="form-group">
<label for="pShort" class="form-label">Short Code</label>
<input type="text" id="pShort" name="short" class="form-control" value="{{ $location->short }}" />
</div>
<div class="form-group">
<label for="pLong" class="form-label">Description</label>
<textarea id="pLong" name="long" class="form-control" rows="4">{{ $location->long }}</textarea>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button name="action" value="delete" class="btn btn-sm btn-danger pull-left muted muted-hover"><i class="fa fa-trash-o"></i></button>
<button name="action" value="edit" class="btn btn-sm btn-primary pull-right">Save</button>
</div>
</form>
</div>
</div>
<div class="col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Nodes</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tr>
<th>ID</th>
<th>Name</th>
<th>FQDN</th>
<th>Servers</th>
</tr>
@foreach($location->nodes as $node)
<tr>
<td><code>{{ $node->id }}</code></td>
<td><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
<td><code>{{ $node->fqdn }}</code></td>
<td>{{ $node->servers->count() }}</td>
</tr>
@endforeach
</table>
</div>
</div>
</div>
</div>
@endsection

View File

@ -91,9 +91,14 @@
</a>
</li>
<li class="header">MANAGEMENT</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.servers') ?: 'active' }}">
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.databases') ?: 'active' }}">
<a href="{{ route('admin.servers') }}">
<i class="fa fa-server"></i> <span>Servers</span>
<i class="fa fa-database"></i> <span>Databases</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.locations') ?: 'active' }}">
<a href="{{ route('admin.locations') }}">
<i class="fa fa-globe"></i> <span>Locations</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.nodes') ?: 'active' }}">
@ -101,6 +106,11 @@
<i class="fa fa-sitemap"></i> <span>Nodes</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.servers') ?: 'active' }}">
<a href="{{ route('admin.servers') }}">
<i class="fa fa-server"></i> <span>Servers</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.users') ?: 'active' }}">
<a href="{{ route('admin.users') }}">
<i class="fa fa-users"></i> <span>Users</span>