Merge pull request #76 from Pterodactyl/feature/laravel-5.3

Laravel 5.3
This commit is contained in:
Dane Everitt 2016-09-03 17:12:58 -04:00 committed by GitHub
commit 193acaa6ea
38 changed files with 735 additions and 1596 deletions

View File

@ -17,3 +17,6 @@ This file is a running track of new features and fixes to each version of the pa
* [Security Patch] Fixes listing of server variables for server. Previously a bug made it possible to view settings for all servers, even if the user didn't own that server. (#69)
* Prevent calling daemon until database call has been confirmed when changing default connection.
* Fixes a few display issues relating to subusers and database management.
### General
* Update Laravel to version `5.3` and update dependencies. **[BREAKING]** This removes the remote API from the panel due to Dingo API instability. This message will be removed when it is added back.

View File

@ -7,12 +7,7 @@ use DisplayException;
use DisplayValidationException;
use AccountNotFoundException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
@ -23,10 +18,12 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontReport = [
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
AuthorizationException::class,
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
/**
@ -37,9 +34,9 @@ class Handler extends ExceptionHandler
* @param \Exception $e
* @return void
*/
public function report(Exception $e)
public function report(Exception $exception)
{
return parent::report($e);
return parent::report($exception);
}
/**
@ -83,4 +80,20 @@ class Handler extends ExceptionHandler
return parent::render($request, $e);
}
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('/auth/login');
}
}

View File

@ -1,32 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use Dingo\Api\Routing\Helpers;
use Illuminate\Routing\Controller;
class BaseController extends Controller
{
use Helpers;
}

View File

@ -1,64 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use DB;
use Illuminate\Http\Request;
use Pterodactyl\Models\Location;
/**
* @Resource("Servers")
*/
class LocationController extends BaseController
{
public function __construct()
{
//
}
/**
* List All Locations
*
* Lists all locations currently on the system.
*
* @Get("/locations")
* @Versions({"v1"})
* @Response(200)
*/
public function getLocations(Request $request)
{
$locations = Location::select('locations.*', DB::raw('GROUP_CONCAT(nodes.id) as nodes'))
->join('nodes', 'locations.id', '=', 'nodes.location')
->groupBy('locations.id')
->get();
foreach($locations as &$location) {
$location->nodes = explode(',', $location->nodes);
}
return $locations;
}
}

View File

@ -1,198 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use Illuminate\Http\Request;
use Pterodactyl\Models;
use Pterodactyl\Transformers\NodeTransformer;
use Pterodactyl\Repositories\NodeRepository;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Exceptions\DisplayException;
use Dingo\Api\Exception\ResourceException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
/**
* @Resource("Servers")
*/
class NodeController extends BaseController
{
public function __construct()
{
//
}
/**
* List All Nodes
*
* Lists all nodes currently on the system.
*
* @Get("/nodes/{?page}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("page", type="integer", description="The page of results to view.", default=1)
* })
* @Response(200)
*/
public function getNodes(Request $request)
{
$nodes = Models\Node::paginate(50);
return $this->response->paginator($nodes, new NodeTransformer);
}
/**
* Create a New Node
*
* @Post("/nodes")
* @Versions({"v1"})
* @Transaction({
* @Request({
* 'name' => 'My API Node',
* 'location' => 1,
* 'public' => 1,
* 'fqdn' => 'daemon.wuzzle.woo',
* 'scheme' => 'https',
* 'memory' => 10240,
* 'memory_overallocate' => 100,
* 'disk' => 204800,
* 'disk_overallocate' => -1,
* 'daemonBase' => '/srv/daemon-data',
* 'daemonSFTP' => 2022,
* 'daemonListen' => 8080
* }, headers={"Authorization": "Bearer <jwt-token>"}),
* @Response(201),
* @Response(422, body={
* "message": "A validation error occured.",
* "errors": {},
* "status_code": 422
* }),
* @Response(503, body={
* "message": "There was an error while attempting to add this node to the system.",
* "status_code": 503
* })
* })
*/
public function postNode(Request $request)
{
try {
$node = new NodeRepository;
$new = $node->create($request->all());
return $this->response->created(route('api.nodes.view', [
'id' => $new
]));
} catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $e) {
throw new BadRequestHttpException('There was an error while attempting to add this node to the system.');
}
}
/**
* List Specific Node
*
* Lists specific fields about a server or all fields pertaining to that node.
*
* @Get("/nodes/{id}/{?fields}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the node to get information on."),
* @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.")
* })
* @Response(200)
*/
public function getNode(Request $request, $id, $fields = null)
{
$query = Models\Node::where('id', $id);
if (!is_null($request->input('fields'))) {
foreach(explode(',', $request->input('fields')) as $field) {
if (!empty($field)) {
$query->addSelect($field);
}
}
}
try {
if (!$query->first()) {
throw new NotFoundHttpException('No node by that ID was found.');
}
return $query->first();
} catch (NotFoundHttpException $ex) {
throw $ex;
} catch (\Exception $ex) {
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
}
}
/**
* List Node Allocations
*
* Returns a listing of all node allocations.
*
* @Get("/nodes/{id}/allocations")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the node to get allocations for."),
* })
* @Response(200)
*/
public function getNodeAllocations(Request $request, $id)
{
$allocations = Models\Allocation::select('ip', 'port', 'assigned_to')->where('node', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get();
if ($allocations->count() < 1) {
throw new NotFoundHttpException('No allocations where found for the requested node.');
}
return $allocations;
}
/**
* Delete Node
*
* @Delete("/nodes/{id}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the node."),
* })
* @Response(204)
*/
public function deleteNode(Request $request, $id)
{
try {
$node = new NodeRepository;
$node->delete($id);
return $this->response->noContent();
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch(\Exception $e) {
throw new ServiceUnavailableHttpException('An error occured while attempting to delete this node.');
}
}
}

View File

@ -1,200 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use Illuminate\Http\Request;
use Pterodactyl\Models;
use Pterodactyl\Transformers\ServerTransformer;
use Pterodactyl\Repositories\ServerRepository;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Exceptions\DisplayException;
use Dingo\Api\Exception\ResourceException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
/**
* @Resource("Servers")
*/
class ServerController extends BaseController
{
public function __construct()
{
//
}
/**
* List All Servers
*
* Lists all servers currently on the system.
*
* @Get("/servers/{?page}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("page", type="integer", description="The page of results to view.", default=1)
* })
* @Response(200)
*/
public function getServers(Request $request)
{
$servers = Models\Server::paginate(50);
return $this->response->paginator($servers, new ServerTransformer);
}
/**
* Create Server
*
* @Post("/servers")
* @Versions({"v1"})
* @Parameters({
* @Parameter("page", type="integer", description="The page of results to view.", default=1)
* })
* @Response(201)
*/
public function postServer(Request $request)
{
try {
$server = new ServerRepository;
$new = $server->create($request->all());
return $this->response->created(route('api.servers.view', [
'id' => $new
]));
} catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $e) {
throw new BadRequestHttpException('There was an error while attempting to add this server to the system.');
}
}
/**
* List Specific Server
*
* Lists specific fields about a server or all fields pertaining to that server.
*
* @Get("/servers/{id}{?fields}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the server to get information on."),
* @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.")
* })
* @Response(200)
*/
public function getServer(Request $request, $id)
{
$query = Models\Server::where('id', $id);
if (!is_null($request->input('fields'))) {
foreach(explode(',', $request->input('fields')) as $field) {
if (!empty($field)) {
$query->addSelect($field);
}
}
}
try {
if (!$query->first()) {
throw new NotFoundHttpException('No server by that ID was found.');
}
return $query->first();
} catch (NotFoundHttpException $ex) {
throw $ex;
} catch (\Exception $ex) {
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
}
}
/**
* Suspend Server
*
* @Post("/servers/{id}/suspend")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the server."),
* })
* @Response(204)
*/
public function postServerSuspend(Request $request, $id)
{
try {
$server = new ServerRepository;
$server->suspend($id);
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $ex) {
throw new ServiceUnavailableHttpException('An error occured while attempting to suspend this server instance.');
}
}
/**
* Unsuspend Server
*
* @Post("/servers/{id}/unsuspend")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the server."),
* })
* @Response(204)
*/
public function postServerUnsuspend(Request $request, $id)
{
try {
$server = new ServerRepository;
$server->unsuspend($id);
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $ex) {
throw new ServiceUnavailableHttpException('An error occured while attempting to unsuspend this server instance.');
}
}
/**
* Delete Server
*
* @Delete("/servers/{id}/{force}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the server."),
* @Parameter("force", type="string", required=false, description="Use 'force' if the server should be removed regardless of daemon response."),
* })
* @Response(204)
*/
public function deleteServer(Request $request, $id, $force = null)
{
try {
$server = new ServerRepository;
$server->deleteServer($id, $force);
return $this->response->noContent();
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch(\Exception $e) {
throw new ServiceUnavailableHttpException('An error occured while attempting to delete this server.');
}
}
}

View File

@ -1,68 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use Illuminate\Http\Request;
use Pterodactyl\Models;
use Pterodactyl\Transformers\ServiceTransformer;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* @Resource("Services")
*/
class ServiceController extends BaseController
{
public function __construct()
{
//
}
public function getServices(Request $request)
{
return Models\Service::all();
}
public function getService(Request $request, $id)
{
$service = Models\Service::find($id);
if (!$service) {
throw new NotFoundHttpException('No service by that ID was found.');
}
$options = Models\ServiceOptions::select('id', 'name', 'description', 'tag', 'docker_image')->where('parent_service', $service->id)->get();
foreach($options as &$opt) {
$opt->variables = Models\ServiceVariables::where('option_id', $opt->id)->get();
}
return [
'service' => $service,
'options' => $options
];
}
}

View File

@ -1,202 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\API;
use Illuminate\Http\Request;
use Dingo\Api\Exception\ResourceException;
use Pterodactyl\Models;
use Pterodactyl\Transformers\UserTransformer;
use Pterodactyl\Repositories\UserRepository;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Exceptions\DisplayException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
/**
* @Resource("Users")
*/
class UserController extends BaseController
{
/**
* List All Users
*
* Lists all users currently on the system.
*
* @Get("/users/{?page}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("page", type="integer", description="The page of results to view.", default=1)
* })
* @Response(200)
*/
public function getUsers(Request $request)
{
$users = Models\User::paginate(50);
return $this->response->paginator($users, new UserTransformer);
}
/**
* List Specific User
*
* Lists specific fields about a user or all fields pertaining to that user.
*
* @Get("/users/{id}/{fields}")
* @Versions({"v1"})
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the user to get information on."),
* @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.")
* })
* @Response(200)
*/
public function getUser(Request $request, $id)
{
$query = Models\User::where('id', $id);
if (!is_null($request->input('fields'))) {
foreach(explode(',', $request->input('fields')) as $field) {
if (!empty($field)) {
$query->addSelect($field);
}
}
}
try {
if (!$query->first()) {
throw new NotFoundHttpException('No user by that ID was found.');
}
return $query->first();
} catch (NotFoundHttpException $ex) {
throw $ex;
} catch (\Exception $ex) {
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
}
}
/**
* Create a New User
*
* @Post("/users")
* @Versions({"v1"})
* @Transaction({
* @Request({
* "email": "foo@example.com",
* "password": "foopassword",
* "admin": false
* }, headers={"Authorization": "Bearer <jwt-token>"}),
* @Response(201),
* @Response(422, body={
* "message": "A validation error occured.",
* "errors": {
* "email": {"The email field is required."},
* "password": {"The password field is required."},
* "admin": {"The admin field is required."}
* },
* "status_code": 422
* })
* })
*/
public function postUser(Request $request)
{
try {
$user = new UserRepository;
$create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'));
return $this->response->created(route('api.users.view', [
'id' => $create
]));
} catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $ex) {
throw new ServiceUnavailableHttpException('Unable to create a user on the system due to an error.');
}
}
/**
* Update an Existing User
*
* The data sent in the request will be used to update the existing user on the system.
*
* @Patch("/users/{id}")
* @Versions({"v1"})
* @Transaction({
* @Request({
* "email": "new@email.com"
* }, headers={"Authorization": "Bearer <jwt-token>"}),
* @Response(200, body={"email": "new@email.com"}),
* @Response(422)
* })
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the user to modify.")
* })
*/
public function patchUser(Request $request, $id)
{
try {
$user = new UserRepository;
$user->update($id, $request->all());
return Models\User::findOrFail($id);
} catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $ex) {
throw new ServiceUnavailableHttpException('Unable to update a user on the system due to an error.');
}
}
/**
* Delete a User
*
* @Delete("/users/{id}")
* @Versions({"v1"})
* @Transaction({
* @Request(headers={"Authorization": "Bearer <jwt-token>"}),
* @Response(204),
* @Response(422)
* })
* @Parameters({
* @Parameter("id", type="integer", required=true, description="The ID of the user to delete.")
* })
*/
public function deleteUser(Request $request, $id)
{
try {
$user = new UserRepository;
$user->delete($id);
return $this->response->noContent();
} catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage());
} catch (\Exception $ex) {
throw new ServiceUnavailableHttpException('Unable to delete this user due to an error.');
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View File

@ -36,27 +36,28 @@ use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
use AuthenticatesUsers;
/**
* Post-Authentication redirect location.
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectPath = '/';
protected $redirectTo = '/';
/**
* Lockout time for failed login requests.
@ -73,42 +74,13 @@ class AuthController extends Controller
protected $maxLoginAttempts = 3;
/**
* Create a new authentication controller instance.
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:8',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => password_hash($data['password']),
]);
$this->middleware('guest', ['except' => 'logout']);
}
/**
@ -117,7 +89,7 @@ class AuthController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postLogin(Request $request)
public function login(Request $request)
{
$this->validate($request, [
@ -125,8 +97,8 @@ class AuthController extends Controller
'password' => 'required',
]);
$throttled = $this->isUsingThrottlesLoginsTrait();
if ($throttled && $this->hasTooManyLoginAttempts($request)) {
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
@ -136,13 +108,11 @@ class AuthController extends Controller
'password' => $request->input('password')
], $request->has('remember'))) {
if ($throttled) {
if (!$lockedOut) {
$this->incrementLoginAttempts($request);
}
return redirect()->route('auth.login')->withInput($request->only('email', 'remember'))->withErrors([
'email' => $this->getFailedLoginMessage(),
]);
return $this->sendFailedLoginResponse($request);
}
@ -155,17 +125,17 @@ class AuthController extends Controller
Auth::logout();
if ($throttled) {
if (!$lockedOut) {
$this->incrementLoginAttempts($request);
}
Alert::danger(trans('auth.totp_failed'))->flash();
return redirect()->route('auth.login')->withInput($request->only('email', 'remember'));
return $this->sendFailedLoginResponse($request);
}
}
return $this->handleUserWasAuthenticated($request, $throttled);
return $this->sendLoginResponse($request);
}

View File

@ -1,56 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.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\Auth;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
protected $redirectTo = '/';
/**
* Create a new password controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\User;
use Validator;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View File

@ -20,17 +20,40 @@ class Kernel extends HttpKernel
\Pterodactyl\Http\Middleware\LanguageMiddleware::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\Pterodactyl\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \Pterodactyl\Http\Middleware\Authenticate::class,
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
'server' => \Pterodactyl\Http\Middleware\CheckServer::class,
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
];
}

View File

@ -1,125 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Middleware;
use Crypt;
use IPTools\IP;
use IPTools\Range;
use Pterodactyl\Models\APIKey;
use Pterodactyl\Models\APIPermission;
use Illuminate\Http\Request;
use Dingo\Api\Routing\Route;
use Dingo\Api\Auth\Provider\Authorization;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; // 401
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403
use Symfony\Component\HttpKernel\Exception\HttpException; //500
class APISecretToken extends Authorization
{
protected $algo = 'sha256';
protected $permissionAllowed = false;
protected $method = '';
protected $url = '';
public function __construct()
{
//
}
public function getAuthorizationMethod()
{
return 'Authorization';
}
public function authenticate(Request $request, Route $route)
{
if (!$request->bearerToken() || empty($request->bearerToken())) {
throw new UnauthorizedHttpException('The authentication header was missing or malformed');
}
list($public, $hashed) = explode('.', $request->bearerToken());
$key = APIKey::where('public', $public)->first();
if (!$key) {
throw new AccessDeniedHttpException('Invalid API Key.');
}
// Check for Resource Permissions
if (!empty($request->route()->getName())) {
if(!is_null($key->allowed_ips)) {
$inRange = false;
foreach(json_decode($key->allowed_ips) as $ip) {
if (Range::parse($ip)->contains(new IP($request->ip()))) {
$inRange = true;
break;
}
}
if (!$inRange) {
throw new AccessDeniedHttpException('This IP address <' . $request->ip() . '> does not have permission to use this API key.');
}
}
foreach(APIPermission::where('key_id', $key->id)->get() as &$row) {
if ($row->permission === '*' || $row->permission === $request->route()->getName()) {
$this->permissionAllowed = true;
continue;
}
}
if (!$this->permissionAllowed) {
throw new AccessDeniedHttpException('You do not have permission to access this resource.');
}
}
try {
$decrypted = Crypt::decrypt($key->secret);
} catch (\Illuminate\Contracts\Encryption\DecryptException $ex) {
throw new HttpException('There was an error while attempting to check your secret key.');
}
$this->method = strtoupper($request->method());
$this->url = urldecode($request->fullUrl());
if($this->_generateHMAC($request->getContent(), $decrypted) !== base64_decode($hashed)) {
throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.');
}
return true;
}
protected function _generateHMAC($body, $key)
{
$data = $this->method . $this->url . $body;
return hash_hmac($this->algo, $data, $key, true);
}
}

View File

@ -3,38 +3,21 @@
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next)
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->check()) {
if (Auth::guard($guard)->check()) {
return redirect('/');
}

View File

@ -1,150 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Routes;
use Pterodactyl\Models;
use Illuminate\Routing\Router;
class APIRoutes
{
public function map(Router $router) {
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', ['middleware' => 'api.auth'], function ($api) {
/**
* User Routes
*/
$api->get('users', [
'as' => 'api.users',
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@getUsers'
]);
$api->post('users', [
'as' => 'api.users.post',
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@postUser'
]);
$api->get('users/{id}', [
'as' => 'api.users.view',
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@getUser'
]);
$api->patch('users/{id}', [
'as' => 'api.users.patch',
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@patchUser'
]);
$api->delete('users/{id}', [
'as' => 'api.users.delete',
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@deleteUser'
]);
/**
* Server Routes
*/
$api->get('servers', [
'as' => 'api.servers',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@getServers'
]);
$api->post('servers', [
'as' => 'api.servers.post',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServer'
]);
$api->get('servers/{id}', [
'as' => 'api.servers.view',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@getServer'
]);
$api->post('servers/{id}/suspend', [
'as' => 'api.servers.suspend',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServerSuspend'
]);
$api->post('servers/{id}/unsuspend', [
'as' => 'api.servers.unsuspend',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServerUnsuspend'
]);
$api->delete('servers/{id}/{force?}', [
'as' => 'api.servers.delete',
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@deleteServer'
]);
/**
* Node Routes
*/
$api->get('nodes', [
'as' => 'api.nodes',
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNodes'
]);
$api->post('nodes', [
'as' => 'api.nodes.post',
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@postNode'
]);
$api->get('nodes/{id}', [
'as' => 'api.nodes.view',
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNode'
]);
$api->get('nodes/{id}/allocations', [
'as' => 'api.nodes.view_allocations',
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNodeAllocations'
]);
$api->delete('nodes/{id}', [
'as' => 'api.nodes.delete',
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@deleteNode'
]);
/**
* Location Routes
*/
$api->get('locations', [
'as' => 'api.locations',
'uses' => 'Pterodactyl\Http\Controllers\API\LocationController@getLocations'
]);
/**
* Service Routes
*/
$api->get('services', [
'as' => 'api.services',
'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@getServices'
]);
$api->get('services/{id}', [
'as' => 'api.services.view',
'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@getService'
]);
});
}
}

View File

@ -28,6 +28,7 @@ use Illuminate\Routing\Router;
use Request;
use Pterodactyl\Models\User as User;
use Auth;
class AuthRoutes {
public function map(Router $router) {
@ -42,39 +43,39 @@ class AuthRoutes {
// Display Login Page
$router->get('login', [
'as' => 'auth.login',
'uses' => 'Auth\AuthController@getLogin'
'uses' => 'Auth\LoginController@showLoginForm'
]);
// Handle Login
$router->post('login', [
'uses' => 'Auth\AuthController@postLogin'
'uses' => 'Auth\LoginController@login'
]);
// Determine if we need to ask for a TOTP Token
$router->post('login/totp', [
'uses' => 'Auth\AuthController@checkTotp'
'uses' => 'Auth\LoginController@checkTotp'
]);
// Show Password Reset Form
$router->get('password', [
'as' => 'auth.password',
'uses' => 'Auth\PasswordController@getEmail'
'uses' => 'Auth\ForgotPasswordController@showLinkRequestForm'
]);
// Handle Password Reset
$router->post('password', [
'uses' => 'Auth\PasswordController@postEmail'
'uses' => 'Auth\ForgotPasswordController@sendResetLinkEmail'
]);
// Show Verification Checkpoint
$router->get('password/reset/{token}', [
'as' => 'auth.reset',
'uses' => 'Auth\PasswordController@getReset'
'uses' => 'Auth\ResetPasswordController@showResetForm'
]);
// Handle Verification
$router->post('password/reset', [
'uses' => 'Auth\PasswordController@postReset'
'uses' => 'Auth\ResetPasswordController@reset'
]);
});
@ -83,7 +84,7 @@ class AuthRoutes {
$router->get('auth/logout', [
'as' => 'auth.logout',
'middleware' => 'auth',
'uses' => 'Auth\AuthController@getLogout'
'uses' => 'Auth\LoginController@logout'
]);
}

View File

@ -28,8 +28,10 @@ use Google2FA;
use Pterodactyl\Exceptions\AccountNotFoundException;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Models\Permission;
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
use Illuminate\Auth\Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
@ -41,7 +43,7 @@ class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
use Authenticatable, Authorizable, CanResetPassword, Notifiable;
/**
* The table associated with the model.
@ -124,4 +126,16 @@ class User extends Model implements AuthenticatableContract,
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Pterodactyl\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class SendPasswordReset extends Notification
{
use Queueable;
/**
* The password reset token.
*
* @var string
*/
public $token;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Reset Password')
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url('auth/password/reset', $this->token))
->line('If you did not request a password reset, no further action is required.');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
/*
* Authenticate the user's personal channel...
*/
Broadcast::channel('App.User.*', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
}
}

View File

@ -2,7 +2,7 @@
namespace Pterodactyl\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
@ -21,12 +21,11 @@ class EventServiceProvider extends ServiceProvider
/**
* Register any other events for your application.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
public function boot()
{
parent::boot($events);
parent::boot();
//
}

View File

@ -2,7 +2,7 @@
namespace Pterodactyl\Providers;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
@ -22,25 +22,58 @@ class RouteServiceProvider extends ServiceProvider
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
public function boot()
{
//
parent::boot($router);
parent::boot();
}
/**
* Define the routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function map(Router $router)
public function map()
{
$router->group(['namespace' => $this->namespace], function ($router) {
$this->mapWebRoutes();
$this->mapApiRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::group([
// 'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
foreach (glob(app_path('Http//Routes') . '/*.php') as $file) {
$this->app->make('Pterodactyl\\Http\\Routes\\' . basename($file, '.php'))->map($router);
}
});
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::group([
'middleware' => 'api',
'namespace' => $this->namespace,
'prefix' => 'api',
], function ($router) {
foreach (glob(app_path('Http//Routes//Api') . '/*.php') as $file) {
$this->app->make('Pterodactyl\\Http\\Routes\\Api\\' . basename($file, '.php'))->map($router);
}
});
}
}

View File

@ -1,42 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Transformers;
use Pterodactyl\Models\Node;
use League\Fractal\TransformerAbstract;
class NodeTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* @return array
*/
public function transform(Node $node)
{
return $node;
}
}

View File

@ -1,42 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Transformers;
use Pterodactyl\Models\Server;
use League\Fractal\TransformerAbstract;
class ServerTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* @return array
*/
public function transform(Server $server)
{
return $server;
}
}

View File

@ -1,42 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Transformers;
use Pterodactyl\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* @return array
*/
public function transform(User $user)
{
return $user;
}
}

View File

@ -16,30 +16,26 @@
}
],
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*",
"barryvdh/laravel-debugbar": "^2.0",
"dingo/api": "1.0.x-dev",
"dingo/blueprint": "0.2.x-dev",
"doctrine/dbal": "^2.5",
"guzzlehttp/guzzle": "^6.1",
"pragmarx/google2fa": "^0.7.1",
"php": ">=5.6.4",
"laravel/framework": "5.3.*",
"barryvdh/laravel-debugbar": "^2.2.3",
"doctrine/dbal": "^2.5.4",
"guzzlehttp/guzzle": "^6.2.1",
"pragmarx/google2fa": "^1.0.1",
"webpatser/laravel-uuid": "^2.0",
"prologue/alerts": "^0.4.0",
"s1lentium/iptools": "^1.0",
"s1lentium/iptools": "^1.1.0",
"edvinaskrucas/settings": "^2.0",
"igaster/laravel-theme": "^1.1",
"igaster/laravel-theme": "^1.1.3",
"nesbot/carbon": "^1.21",
"mtdowling/cron-expression": "^1.1"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1",
"symfony/css-selector": "~3.0",
"symfony/dom-crawler": "~3.0",
"laravel/homestead": "^3.0"
"phpunit/phpunit": "~5.0",
"symfony/css-selector": "3.1.*",
"symfony/dom-crawler": "3.1.*"
},
"autoload": {
"classmap": [
@ -56,19 +52,17 @@
},
"scripts": {
"post-root-package-install": [
"php -r \"copy('.env.example', '.env');\""
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate"
],
"post-install-cmd": [
"php artisan clear-compiled",
"Illuminate\\Foundation\\ComposerScripts::postInstall",
"php artisan optimize"
],
"pre-update-cmd": [
"php artisan clear-compiled"
],
"post-update-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan optimize"
],
"setup-dev": [
@ -84,7 +78,7 @@
],
"setup": [
"composer install --ansi --no-dev",
"php -r \"copy('.env.example', '.env');\"",
"php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"php artisan key:generate",
"php artisan pterodactyl:env",
"php artisan migrate",

View File

@ -1,209 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Standards Tree
|--------------------------------------------------------------------------
|
| Versioning an API with Dingo revolves around content negotiation and
| custom MIME types. A custom type will belong to one of three
| standards trees, the Vendor tree (vnd), the Personal tree
| (prs), and the Unregistered tree (x).
|
| By default the Unregistered tree (x) is used, however, should you wish
| to you can register your type with the IANA. For more details:
| https://tools.ietf.org/html/rfc6838
|
*/
'standardsTree' => env('API_STANDARDS_TREE', 'x'),
/*
|--------------------------------------------------------------------------
| API Subtype
|--------------------------------------------------------------------------
|
| Your subtype will follow the standards tree you use when used in the
| "Accept" header to negotiate the content type and version.
|
| For example: Accept: application/x.SUBTYPE.v1+json
|
*/
'subtype' => env('API_SUBTYPE', 'pterodactyl'),
/*
|--------------------------------------------------------------------------
| Default API Version
|--------------------------------------------------------------------------
|
| This is the default version when strict mode is disabled and your API
| is accessed via a web browser. It's also used as the default version
| when generating your APIs documentation.
|
*/
'version' => env('API_VERSION', 'v1'),
/*
|--------------------------------------------------------------------------
| Default API Prefix
|--------------------------------------------------------------------------
|
| A default prefix to use for your API routes so you don't have to
| specify it for each group.
|
*/
'prefix' => env('API_PREFIX', null),
/*
|--------------------------------------------------------------------------
| Default API Domain
|--------------------------------------------------------------------------
|
| A default domain to use for your API routes so you don't have to
| specify it for each group.
|
*/
'domain' => env('API_DOMAIN', null),
/*
|--------------------------------------------------------------------------
| Name
|--------------------------------------------------------------------------
|
| When documenting your API using the API Blueprint syntax you can
| configure a default name to avoid having to manually specify
| one when using the command.
|
*/
'name' => env('API_NAME', 'Pterodactyl Panel API'),
/*
|--------------------------------------------------------------------------
| Conditional Requests
|--------------------------------------------------------------------------
|
| Globally enable conditional requests so that an ETag header is added to
| any successful response. Subsequent requests will perform a check and
| will return a 304 Not Modified. This can also be enabled or disabled
| on certain groups or routes.
|
*/
'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true),
/*
|--------------------------------------------------------------------------
| Strict Mode
|--------------------------------------------------------------------------
|
| Enabling strict mode will require clients to send a valid Accept header
| with every request. This also voids the default API version, meaning
| your API will not be browsable via a web browser.
|
*/
'strict' => env('API_STRICT', false),
/*
|--------------------------------------------------------------------------
| Debug Mode
|--------------------------------------------------------------------------
|
| Enabling debug mode will result in error responses caused by thrown
| exceptions to have a "debug" key that will be populated with
| more detailed information on the exception.
|
*/
'debug' => env('API_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Generic Error Format
|--------------------------------------------------------------------------
|
| When some HTTP exceptions are not caught and dealt with the API will
| generate a generic error response in the format provided. Any
| keys that aren't replaced with corresponding values will be
| removed from the final response.
|
*/
'errorFormat' => [
'message' => ':message',
'errors' => ':errors',
'code' => ':code',
'status_code' => ':status_code',
'debug' => ':debug',
],
/*
|--------------------------------------------------------------------------
| Authentication Providers
|--------------------------------------------------------------------------
|
| The authentication providers that should be used when attempting to
| authenticate an incoming API request.
|
*/
'auth' => [
'custom' => 'Pterodactyl\Http\Middleware\APISecretToken'
],
/*
|--------------------------------------------------------------------------
| Throttling / Rate Limiting
|--------------------------------------------------------------------------
|
| Consumers of your API can be limited to the amount of requests they can
| make. You can create your own throttles or simply change the default
| throttles.
|
*/
'throttling' => [
],
/*
|--------------------------------------------------------------------------
| Response Transformer
|--------------------------------------------------------------------------
|
| Responses can be transformed so that they are easier to format. By
| default a Fractal transformer will be used to transform any
| responses prior to formatting. You can easily replace
| this with your own transformer.
|
*/
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
/*
|--------------------------------------------------------------------------
| Response Formats
|--------------------------------------------------------------------------
|
| Responses can be returned in multiple formats by registering different
| response formatters. You can also customize an existing response
| formatter.
|
*/
'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'),
'formats' => [
'json' => Dingo\Api\Http\Response\Format\Json::class,
],
];

View File

@ -114,8 +114,6 @@ return [
'providers' => [
Dingo\Api\Provider\LaravelServiceProvider::class,
/*
* Laravel Framework Service Providers...
*/
@ -140,6 +138,7 @@ return [
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
/*
* Application Service Providers...
@ -187,8 +186,6 @@ return [
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Debugbar' => Barryvdh\Debugbar\Facade::class,
'DingoAPI' => Dingo\Api\Facade\API::class,
'DingoRoute' => Dingo\Api\Facade\Route::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
@ -200,6 +197,7 @@ return [
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,

View File

@ -31,7 +31,7 @@
@section('content')
<div class="col-md-8">
<form action="/auth/password" method="POST">
<form action="{{ url('/auth/password') }}" method="POST">
<legend>{{ trans('auth.resetpassword') }}</legend>
<fieldset>
@if (session('status'))
@ -39,10 +39,15 @@
<strong>{{ trans('strings.success') }}!</strong> {{ trans('auth.emailsent') }}
</div>
@endif
<div class="form-group">
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="control-label">{{ trans('strings.email') }}</label>
<div>
<input type="text" class="form-control" name="email" id="email" value="{{ old('email') }}" placeholder="{{ trans('strings.email') }}" />
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">

View File

@ -31,26 +31,41 @@
@section('content')
<div class="col-md-8">
<form action="/auth/password/reset" method="POST">
<form action="{{ url('/auth/password/reset') }}" method="POST">
<legend>{{ trans('auth.resetpassword') }}</legend>
<fieldset>
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group">
<label for="email" class="control-label">{{ trans('strings.email') }}</label>
<div>
<input type="text" class="form-control" name="email" id="email" value="{{ old('email') }}" placeholder="{{ trans('strings.email') }}" />
<input type="text" class="form-control" name="email" id="email" value="{{ $email or old('email') }}" required autofocus placeholder="{{ trans('strings.email') }}" />
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">{{ trans('strings.password') }}</label>
<div>
<input type="password" class="form-control" name="password" id="password" placeholder="{{ trans('strings.password') }}" />
<input type="password" class="form-control" name="password" id="password" required placeholder="{{ trans('strings.password') }}" />
@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<label for="password_confirmation" class="control-label">{{ trans('auth.confirmpassword') }}</label>
<div>
<input type="password" class="form-control" id="password_confirmation" name="password_confirmation" />
<input type="password" class="form-control" id="password_confirmation" required name="password_confirmation" />
@if ($errors->has('password_confirmation'))
<span class="help-block">
<strong>{{ $errors->first('password_confirmation') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">

View File

@ -1,32 +0,0 @@
{{-- Copyright (c) 2015 - 2016 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. --}}
<html>
<head>
<title>Pterodactyl Lost Password Recovery</title>
</head>
<body>
<center><h1>Pterodactyl Lost Password Recovery</h1></center>
<p>Hello there! You are receiving this email because you requested a new password for your Pterodactyl account.</p>
<p>Please click the link below to confirm that you wish to change your password. If you did not make this request, or do not wish to continue simply ignore this email and nothing will happen. <strong>This link will expire in 1 hour.</strong></p>
<p><a href="{{ url('auth/password/reset/'.$token) }}">{{ url('auth/password/reset/'.$token) }}</a></p>
<p>Please do not hesitate to contact us if you belive something is wrong.
<p>Thanks!<br />Pterodactyl</p>
</body>
</html>

View File

@ -0,0 +1,22 @@
<?php
if (! empty($greeting)) {
echo $greeting, "\n\n";
} else {
echo $level == 'error' ? 'Whoops!' : 'Hello!', "\n\n";
}
if (! empty($introLines)) {
echo implode("\n", $introLines), "\n\n";
}
if (isset($actionText)) {
echo "{$actionText}: {$actionUrl}", "\n\n";
}
if (! empty($outroLines)) {
echo implode("\n", $outroLines), "\n\n";
}
echo 'Regards,', "\n";
echo config('app.name'), "\n";

View File

@ -0,0 +1,192 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css" rel="stylesheet" media="all">
/* Media Queries */
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
</head>
<?php
$style = [
/* Layout ------------------------------ */
'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;',
'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;',
/* Masthead ----------------------- */
'email-masthead' => 'padding: 25px 0; text-align: center;',
'email-masthead_name' => 'font-size: 16px; font-weight: bold; color: #2F3133; text-decoration: none; text-shadow: 0 1px 0 white;',
'email-body' => 'width: 100%; margin: 0; padding: 0; border-top: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2; background-color: #FFF;',
'email-body_inner' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0;',
'email-body_cell' => 'padding: 35px;',
'email-footer' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;',
'email-footer_cell' => 'color: #AEAEAE; padding: 35px; text-align: center;',
/* Body ------------------------------ */
'body_action' => 'width: 100%; margin: 30px auto; padding: 0; text-align: center;',
'body_sub' => 'margin-top: 25px; padding-top: 25px; border-top: 1px solid #EDEFF2;',
/* Type ------------------------------ */
'anchor' => 'color: #3869D4;',
'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;',
'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;',
'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;',
'paragraph-center' => 'text-align: center;',
/* Buttons ------------------------------ */
'button' => 'display: block; display: inline-block; width: 200px; min-height: 20px; padding: 10px;
background-color: #3869D4; border-radius: 3px; color: #ffffff; font-size: 15px; line-height: 25px;
text-align: center; text-decoration: none; -webkit-text-size-adjust: none;',
'button--green' => 'background-color: #22BC66;',
'button--red' => 'background-color: #dc4d2f;',
'button--blue' => 'background-color: #3869D4;',
];
?>
<?php $fontFamily = 'font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;'; ?>
<body style="{{ $style['body'] }}">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="{{ $style['email-wrapper'] }}" align="center">
<table width="100%" cellpadding="0" cellspacing="0">
<!-- Logo -->
<tr>
<td style="{{ $style['email-masthead'] }}">
<a style="{{ $fontFamily }} {{ $style['email-masthead_name'] }}" href="{{ url('/') }}" target="_blank">
{{ config('app.name') }}
</a>
</td>
</tr>
<!-- Email Body -->
<tr>
<td style="{{ $style['email-body'] }}" width="100%">
<table style="{{ $style['email-body_inner'] }}" align="center" width="570" cellpadding="0" cellspacing="0">
<tr>
<td style="{{ $fontFamily }} {{ $style['email-body_cell'] }}">
<!-- Greeting -->
<h1 style="{{ $style['header-1'] }}">
@if (! empty($greeting))
{{ $greeting }}
@else
@if ($level == 'error')
Whoops!
@else
Hello!
@endif
@endif
</h1>
<!-- Intro -->
@foreach ($introLines as $line)
<p style="{{ $style['paragraph'] }}">
{{ $line }}
</p>
@endforeach
<!-- Action Button -->
@if (isset($actionText))
<table style="{{ $style['body_action'] }}" align="center" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<?php
switch ($level) {
case 'success':
$actionColor = 'button--green';
break;
case 'error':
$actionColor = 'button--red';
break;
default:
$actionColor = 'button--blue';
}
?>
<a href="{{ $actionUrl }}"
style="{{ $fontFamily }} {{ $style['button'] }} {{ $style[$actionColor] }}"
class="button"
target="_blank">
{{ $actionText }}
</a>
</td>
</tr>
</table>
@endif
<!-- Outro -->
@foreach ($outroLines as $line)
<p style="{{ $style['paragraph'] }}">
{{ $line }}
</p>
@endforeach
<!-- Salutation -->
<p style="{{ $style['paragraph'] }}">
Regards,<br>{{ config('app.name') }}
</p>
<!-- Sub Copy -->
@if (isset($actionText))
<table style="{{ $style['body_sub'] }}">
<tr>
<td style="{{ $fontFamily }}">
<p style="{{ $style['paragraph-sub'] }}">
If youre having trouble clicking the "{{ $actionText }}" button,
copy and paste the URL below into your web browser:
</p>
<p style="{{ $style['paragraph-sub'] }}">
<a style="{{ $style['anchor'] }}" href="{{ $actionUrl }}" target="_blank">
{{ $actionUrl }}
</a>
</p>
</td>
</tr>
</table>
@endif
</td>
</tr>
</table>
</td>
</tr>
<!-- Footer -->
<tr>
<td>
<table style="{{ $style['email-footer'] }}" align="center" width="570" cellpadding="0" cellspacing="0">
<tr>
<td style="{{ $fontFamily }} {{ $style['email-footer_cell'] }}">
<p style="{{ $style['paragraph-sub'] }}">
&copy; {{ date('Y') }}
<a style="{{ $style['anchor'] }}" href="{{ url('/') }}" target="_blank">{{ config('app.name') }}</a>.
All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,36 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="page-item disabled"><span class="page-link">&laquo;</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Pagination Elements -->
@foreach ($elements as $element)
<!-- "Three Dots" Separator -->
@if (is_string($element))
<li class="page-item disabled"><span class="page-link">{{ $element }}</span></li>
@endif
<!-- Array Of Links -->
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active"><span class="page-link">{{ $page }}</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="page-item disabled"><span class="page-link">&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -0,0 +1,36 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="disabled"><span>&laquo;</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Pagination Elements -->
@foreach ($elements as $element)
<!-- "Three Dots" Separator -->
@if (is_string($element))
<li class="disabled"><span>{{ $element }}</span></li>
@endif
<!-- Array Of Links -->
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="active"><span>{{ $page }}</span></li>
@else
<li><a href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="disabled"><span>&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -0,0 +1,17 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="page-item disabled"><span class="page-link">&laquo;</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="page-item disabled"><span class="page-link">&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -0,0 +1,17 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="disabled"><span>&laquo;</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="disabled"><span>&raquo;</span></li>
@endif
</ul>
@endif