diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 72158193f..d21c8d3c8 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http; +use Pterodactyl\Http\Middleware\MaintenanceMiddleware; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; @@ -108,6 +109,7 @@ class Kernel extends HttpKernel 'can' => Authorize::class, 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, + 'node.maintenance' => MaintenanceMiddleware::class, // Server specific middleware (used for authenticating access to resources) // diff --git a/app/Http/Middleware/MaintenanceMiddleware.php b/app/Http/Middleware/MaintenanceMiddleware.php new file mode 100644 index 000000000..c67a3f051 --- /dev/null +++ b/app/Http/Middleware/MaintenanceMiddleware.php @@ -0,0 +1,44 @@ +response = $response; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + /** @var \Pterodactyl\Models\Server $server */ + $server = $request->attributes->get('server'); + $node = $server->getRelation('node'); + + if ($node->maintenance_mode) { + return $this->response->view('errors.maintenance'); + } + + return $next($request); + } +} diff --git a/app/Models/Node.php b/app/Models/Node.php index 26d9eb443..2643d062a 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -48,6 +48,7 @@ class Node extends Model implements CleansAttributes, ValidableContract 'daemonSFTP' => 'integer', 'behind_proxy' => 'boolean', 'public' => 'boolean', + 'maintenance_mode' => 'boolean', ]; /** @@ -62,7 +63,7 @@ class Node extends Model implements CleansAttributes, ValidableContract 'disk_overallocate', 'upload_size', 'daemonSecret', 'daemonBase', 'daemonSFTP', 'daemonListen', - 'description', + 'description', 'maintenance_mode', ]; /** @@ -111,6 +112,7 @@ class Node extends Model implements CleansAttributes, ValidableContract 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', 'daemonSFTP' => 'numeric|between:1024,65535', 'daemonListen' => 'numeric|between:1024,65535', + 'maintenance_mode' => 'boolean', ]; /** @@ -126,6 +128,7 @@ class Node extends Model implements CleansAttributes, ValidableContract 'daemonBase' => '/srv/daemon-data', 'daemonSFTP' => 2022, 'daemonListen' => 8080, + 'maintenance_mode' => false, ]; /** diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3de307d9a..f0e978116 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -33,7 +33,7 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace . '\Auth') ->group(base_path('routes/auth.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth'])->prefix('/server/{server}') + Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth', 'node.maintenance'])->prefix('/server/{server}') ->namespace($this->namespace . '\Server') ->group(base_path('routes/server.php')); diff --git a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php new file mode 100644 index 000000000..799f1df23 --- /dev/null +++ b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php @@ -0,0 +1,32 @@ +boolean('maintenance_mode')->after('behind_proxy')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn('maintenance_mode'); + }); + } +} diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 2c0f6c62b..01ac79b1e 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -21,6 +21,11 @@ return [ 'header' => 'Server Suspended', 'desc' => 'This server has been suspended and cannot be accessed.', ], + 'maintenance' => [ + 'header' => 'Node Under Maintenance', + 'title' => 'Temporarily Unavailable', + 'desc' => 'This node is under maintenance, therefore your server can temporarily not be accessed.', + ], ], 'index' => [ 'header' => 'Your Servers', diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index 5b9173866..c0bf3f417 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -74,6 +74,7 @@ return [ 'tasks' => 'Tasks', 'seconds' => 'Seconds', 'minutes' => 'Minutes', + 'under_maintenance' => 'Under Maintenance', 'days' => [ 'sun' => 'Sunday', 'mon' => 'Monday', diff --git a/resources/themes/pterodactyl/admin/nodes/index.blade.php b/resources/themes/pterodactyl/admin/nodes/index.blade.php index abaf25e54..b4ea579a1 100644 --- a/resources/themes/pterodactyl/admin/nodes/index.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/index.blade.php @@ -56,7 +56,7 @@ @foreach ($nodes as $node) - {{ $node->name }} + {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} {{ $node->memory }} MB {{ $node->disk }} MB diff --git a/resources/themes/pterodactyl/admin/nodes/view/index.blade.php b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php index 9f385c9d8..71eb346d2 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/index.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php @@ -96,6 +96,17 @@
+ @if($node->maintenance_mode) +
+
+ +
+ This node is under + Maintenance +
+
+
+ @endif
diff --git a/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php index 7de4582f6..5afd65ed4 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php @@ -108,6 +108,20 @@

If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

+
+ +
+
+ maintenance_mode) == false) ? 'checked' : '' }}> + +
+
+ maintenance_mode) == true) ? 'checked' : '' }}> + +
+
+

If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.

+
diff --git a/resources/themes/pterodactyl/base/index.blade.php b/resources/themes/pterodactyl/base/index.blade.php index 95cce6128..28f4c4d57 100644 --- a/resources/themes/pterodactyl/base/index.blade.php +++ b/resources/themes/pterodactyl/base/index.blade.php @@ -64,9 +64,15 @@ @lang('strings.subuser') @endif - - - + @if($server->node->maintenance_mode) + + @lang('strings.under_maintenance') + + @else + + + + @endif @if (! empty($server->description)) diff --git a/resources/themes/pterodactyl/errors/maintenance.blade.php b/resources/themes/pterodactyl/errors/maintenance.blade.php new file mode 100644 index 000000000..8cc8eea27 --- /dev/null +++ b/resources/themes/pterodactyl/errors/maintenance.blade.php @@ -0,0 +1,30 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} +@extends('layouts.error') + +@section('title') + @lang('base.errors.maintenance.header') +@endsection + +@section('content-header') +@endsection + +@section('content') +
+
+
+
+

@lang('base.errors.maintenance.title')

+

@lang('base.errors.maintenance.desc')

+
+ +
+
+
+@endsection diff --git a/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php new file mode 100644 index 000000000..fedeaa0c6 --- /dev/null +++ b/tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php @@ -0,0 +1,70 @@ +response = m::mock(ResponseFactory::class); + } + + /** + * Test that a node not in maintenance mode continues through the request cycle. + */ + public function testHandle() + { + $server = factory(Server::class)->make(); + $node = factory(Node::class)->make(['maintenance' => 0]); + + $server->setRelation('node', $node); + $this->setRequestAttribute('server', $server); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a node in maintenance mode returns an error view. + */ + public function testHandleInMaintenanceMode() + { + $server = factory(Server::class)->make(); + $node = factory(Node::class)->make(['maintenance_mode' => 1]); + + $server->setRelation('node', $node); + $this->setRequestAttribute('server', $server); + + $this->response->shouldReceive('view') + ->once() + ->with('errors.maintenance') + ->andReturn(new Response); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(Response::class, $response); + } + + /** + * @return \Pterodactyl\Http\Middleware\MaintenanceMiddleware + */ + private function getMiddleware(): MaintenanceMiddleware + { + return new MaintenanceMiddleware($this->response); + } +}