Merge pull request #1129 from stanjg/feature/node-maintenance
Added a maintenance mode for nodes
This commit is contained in:
commit
5be4520d37
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http;
|
namespace Pterodactyl\Http;
|
||||||
|
|
||||||
|
use Pterodactyl\Http\Middleware\MaintenanceMiddleware;
|
||||||
use Pterodactyl\Models\ApiKey;
|
use Pterodactyl\Models\ApiKey;
|
||||||
use Illuminate\Auth\Middleware\Authorize;
|
use Illuminate\Auth\Middleware\Authorize;
|
||||||
use Illuminate\Auth\Middleware\Authenticate;
|
use Illuminate\Auth\Middleware\Authenticate;
|
||||||
|
@ -108,6 +109,7 @@ class Kernel extends HttpKernel
|
||||||
'can' => Authorize::class,
|
'can' => Authorize::class,
|
||||||
'bindings' => SubstituteBindings::class,
|
'bindings' => SubstituteBindings::class,
|
||||||
'recaptcha' => VerifyReCaptcha::class,
|
'recaptcha' => VerifyReCaptcha::class,
|
||||||
|
'node.maintenance' => MaintenanceMiddleware::class,
|
||||||
|
|
||||||
// Server specific middleware (used for authenticating access to resources)
|
// Server specific middleware (used for authenticating access to resources)
|
||||||
//
|
//
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||||
|
|
||||||
|
class MaintenanceMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Routing\ResponseFactory
|
||||||
|
*/
|
||||||
|
private $response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MaintenanceMiddleware constructor.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
|
||||||
|
*/
|
||||||
|
public function __construct(ResponseFactory $response)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ class Node extends Model implements CleansAttributes, ValidableContract
|
||||||
'daemonSFTP' => 'integer',
|
'daemonSFTP' => 'integer',
|
||||||
'behind_proxy' => 'boolean',
|
'behind_proxy' => 'boolean',
|
||||||
'public' => 'boolean',
|
'public' => 'boolean',
|
||||||
|
'maintenance_mode' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +63,7 @@ class Node extends Model implements CleansAttributes, ValidableContract
|
||||||
'disk_overallocate', 'upload_size',
|
'disk_overallocate', 'upload_size',
|
||||||
'daemonSecret', 'daemonBase',
|
'daemonSecret', 'daemonBase',
|
||||||
'daemonSFTP', 'daemonListen',
|
'daemonSFTP', 'daemonListen',
|
||||||
'description',
|
'description', 'maintenance_mode',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,6 +112,7 @@ class Node extends Model implements CleansAttributes, ValidableContract
|
||||||
'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/',
|
'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/',
|
||||||
'daemonSFTP' => 'numeric|between:1024,65535',
|
'daemonSFTP' => 'numeric|between:1024,65535',
|
||||||
'daemonListen' => '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',
|
'daemonBase' => '/srv/daemon-data',
|
||||||
'daemonSFTP' => 2022,
|
'daemonSFTP' => 2022,
|
||||||
'daemonListen' => 8080,
|
'daemonListen' => 8080,
|
||||||
|
'maintenance_mode' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,7 +33,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
->namespace($this->namespace . '\Auth')
|
->namespace($this->namespace . '\Auth')
|
||||||
->group(base_path('routes/auth.php'));
|
->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')
|
->namespace($this->namespace . '\Server')
|
||||||
->group(base_path('routes/server.php'));
|
->group(base_path('routes/server.php'));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddMaintenanceToNodes extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('nodes', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,11 @@ return [
|
||||||
'header' => 'Server Suspended',
|
'header' => 'Server Suspended',
|
||||||
'desc' => 'This server has been suspended and cannot be accessed.',
|
'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' => [
|
'index' => [
|
||||||
'header' => 'Your Servers',
|
'header' => 'Your Servers',
|
||||||
|
|
|
@ -74,6 +74,7 @@ return [
|
||||||
'tasks' => 'Tasks',
|
'tasks' => 'Tasks',
|
||||||
'seconds' => 'Seconds',
|
'seconds' => 'Seconds',
|
||||||
'minutes' => 'Minutes',
|
'minutes' => 'Minutes',
|
||||||
|
'under_maintenance' => 'Under Maintenance',
|
||||||
'days' => [
|
'days' => [
|
||||||
'sun' => 'Sunday',
|
'sun' => 'Sunday',
|
||||||
'mon' => 'Monday',
|
'mon' => 'Monday',
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
@foreach ($nodes as $node)
|
@foreach ($nodes as $node)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemonSecret }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/v1"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemonSecret }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/v1"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||||
<td><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
<td>{!! $node->maintenance_mode ? '<span class="label label-warning"><i class="fa fa-wrench"></i></span> ' : '' !!}<a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
||||||
<td>{{ $node->location->short }}</td>
|
<td>{{ $node->location->short }}</td>
|
||||||
<td>{{ $node->memory }} MB</td>
|
<td>{{ $node->memory }} MB</td>
|
||||||
<td>{{ $node->disk }} MB</td>
|
<td>{{ $node->disk }} MB</td>
|
||||||
|
|
|
@ -96,6 +96,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@if($node->maintenance_mode)
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="info-box bg-orange">
|
||||||
|
<span class="info-box-icon"><i class="ion ion-wrench"></i></span>
|
||||||
|
<div class="info-box-content" style="padding: 23px 10px 0;">
|
||||||
|
<span class="info-box-text">This node is under</span>
|
||||||
|
<span class="info-box-number">Maintenance</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="info-box bg-{{ $stats['disk']['css'] }}">
|
<div class="info-box bg-{{ $stats['disk']['css'] }}">
|
||||||
<span class="info-box-icon"><i class="ion ion-ios-folder-outline"></i></span>
|
<span class="info-box-icon"><i class="ion ion-ios-folder-outline"></i></span>
|
||||||
|
|
|
@ -108,6 +108,20 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="text-muted small">If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.</p>
|
<p class="text-muted small">If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-xs-12">
|
||||||
|
<label class="form-label"><span class="label label-warning"><i class="fa fa-wrench"></i></span> Maintenance Mode</label>
|
||||||
|
<div>
|
||||||
|
<div class="radio radio-success radio-inline">
|
||||||
|
<input type="radio" id="pMaintenanceFalse" value="0" name="maintenance_mode" {{ (old('behind_proxy', $node->maintenance_mode) == false) ? 'checked' : '' }}>
|
||||||
|
<label for="pMaintenanceFalse"> Disabled</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio radio-warning radio-inline">
|
||||||
|
<input type="radio" id="pMaintenanceTrue" value="1" name="maintenance_mode" {{ (old('behind_proxy', $node->maintenance_mode) == true) ? 'checked' : '' }}>
|
||||||
|
<label for="pMaintenanceTrue"> Enabled</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted small">If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -64,9 +64,15 @@
|
||||||
<span class="label bg-blue">@lang('strings.subuser')</span>
|
<span class="label bg-blue">@lang('strings.subuser')</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" data-action="status">
|
@if($server->node->maintenance_mode)
|
||||||
<span class="label label-default"><i class="fa fa-refresh fa-fw fa-spin"></i></span>
|
<td class="text-center">
|
||||||
</td>
|
<span class="label label-warning">@lang('strings.under_maintenance')</span>
|
||||||
|
</td>
|
||||||
|
@else
|
||||||
|
<td class="text-center" data-action="status">
|
||||||
|
<span class="label label-default"><i class="fa fa-refresh fa-fw fa-spin"></i></span>
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
</tr>
|
</tr>
|
||||||
@if (! empty($server->description))
|
@if (! empty($server->description))
|
||||||
<tr class="server-description">
|
<tr class="server-description">
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{{-- Pterodactyl - Panel --}}
|
||||||
|
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||||
|
|
||||||
|
{{-- 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')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
|
||||||
|
<div class="box box-danger">
|
||||||
|
<div class="box-body text-center">
|
||||||
|
<h1 class="text-red" style="font-size: 3em !important;font-weight: 100 !important;">@lang('base.errors.maintenance.title')</h1>
|
||||||
|
<p class="text-muted">@lang('base.errors.maintenance.desc')</p>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer with-border">
|
||||||
|
<a href="{{ URL::previous() }}"><button class="btn btn-danger">← @lang('base.errors.return')</button></a>
|
||||||
|
<a href="/"><button class="btn btn-default">@lang('base.errors.home')</button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Http\Middleware;
|
||||||
|
|
||||||
|
use Mockery as m;
|
||||||
|
use Pterodactyl\Models\Node;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Pterodactyl\Models\Server;
|
||||||
|
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||||
|
use Pterodactyl\Http\Middleware\MaintenanceMiddleware;
|
||||||
|
|
||||||
|
class MaintenanceMiddlewareTest extends MiddlewareTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Routing\ResponseFactory|\Mockery\Mock
|
||||||
|
*/
|
||||||
|
private $response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tests.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue