Merge branch 'release/v0.7.8'
This commit is contained in:
commit
28442cead3
70 changed files with 1463 additions and 498 deletions
26
.github/ISSUE_TEMPLATE.md
vendored
26
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,26 +0,0 @@
|
|||
<!---
|
||||
Please take a little time to submit a good issue. It makes our life easier and the issue will be resolved quicker.
|
||||
|
||||
!!! GitHub is NOT the place for difficulties setting up this software. Please use it for bugs and feature requests only. If you have issues setting up the panel or the daemon visit our Discord server: https://pterodactyl.io/discord
|
||||
|
||||
If you are submitting a feature request please remove everything in here. Then give a detailed explanation what you want to have implemented and why that would be a good addition.
|
||||
|
||||
Please also try to give the issue a good title: It should summarize your issue in a few words and help us see what the issue is about in a glance. Things like "Panel is not working" do not help.
|
||||
|
||||
--- You can delete everything above this line. --->
|
||||
|
||||
<!--- Please fill in the following basic information --->
|
||||
* Panel or Daemon:
|
||||
* Version of Panel/Daemon:
|
||||
* Server's OS:
|
||||
* Your Computer's OS & Browser:
|
||||
|
||||
------------------------
|
||||
|
||||
<!---
|
||||
Please provide as much information about your issue as needed. Include precise steps to reproduce the issue and provide logs of the components that didn't work as expected.
|
||||
|
||||
Please provide additional information, depending on what you have issues with:
|
||||
Panel: `php -v` (the php version in use).
|
||||
Daemon: `uname -a` and `docker info` (your kernel version and information regarding docker)
|
||||
--->
|
27
.github/ISSUE_TEMPLATE/---bug-report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/---bug-report.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Create a report to help us resolve a bug or error
|
||||
|
||||
---
|
||||
|
||||
**Background (please complete the following information):**
|
||||
* Panel or Daemon:
|
||||
* Version of Panel/Daemon:
|
||||
* Server's OS:
|
||||
* Your Computer's OS & Browser:
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
Please provide additional information too, depending on what you have issues with:
|
||||
Panel: `php -v` (the php version in use).
|
||||
Daemon: `uname -a` and `docker info` (your kernel version and information regarding docker)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen. If applicable, add screenshots or a recording to help explain your problem.
|
17
.github/ISSUE_TEMPLATE/---feature-request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/---feature-request.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: "\U0001F680 Feature Request"
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
8
.github/ISSUE_TEMPLATE/--installation-help.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/--installation-help.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: "⛔ Installation Help"
|
||||
about: 'Visit our Discord for installation help: https://pterodactyl.io/discord'
|
||||
|
||||
---
|
||||
|
||||
We use GitHub issues only to discuss about Pterodactyl bugs and new features. For
|
||||
this kind of questions about using Pterodactyl, please visit our Discord for assistance: https://pterodactyl.io/discord
|
17
.travis.yml
17
.travis.yml
|
@ -1,5 +1,17 @@
|
|||
language: php
|
||||
dist: trusty
|
||||
git:
|
||||
depth: 3
|
||||
quiet: true
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: TEST_SUITE=Coverage
|
||||
env:
|
||||
matrix:
|
||||
- TEST_SUITE=Unit
|
||||
- TEST_SUITE=Coverage
|
||||
- TEST_SUITE=Integration
|
||||
php:
|
||||
- 7.2
|
||||
sudo: false
|
||||
|
@ -15,8 +27,9 @@ before_script:
|
|||
- cp .env.travis .env
|
||||
- travis_retry composer install --no-interaction --prefer-dist --no-suggest
|
||||
script:
|
||||
- vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-clover coverage.xml tests/Unit
|
||||
- vendor/bin/phpunit tests/Integration
|
||||
- if [ "$TEST_SUITE" = "Unit" ]; then vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit; fi;
|
||||
- if [ "$TEST_SUITE" = "Coverage" ]; then vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-clover coverage.xml tests/Unit; fi;
|
||||
- if [ "$TEST_SUITE" = "Integration" ]; then vendor/bin/phpunit tests/Integration; fi;
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -3,6 +3,27 @@ This file is a running track of new features and fixes to each version of the pa
|
|||
|
||||
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||
|
||||
## v0.7.8 (Derelict Dermodactylus)
|
||||
### Added
|
||||
* Nodes can now be put into maintenance mode to deny access to servers temporarily.
|
||||
* Basic statistics about your panel are now available in the Admin CP.
|
||||
* Added support for using a MySQL socket location for connections rather than a TCP connection. Set a `DB_SOCKET` variable in your `.env` file to use this.
|
||||
|
||||
### Fixed
|
||||
* Hitting Ctrl+Z when editing a file on the web now works as expected.
|
||||
* Logo now links to the correct location on all pages.
|
||||
* Permissions checking to determine if a user can see the task management page now works correctly.
|
||||
* Fixed `pterodactyl.environment_variables` to be used correctly for global environment variables. The wrong config variable name was being using previously.
|
||||
* Fixes tokens being sent to users when their account is created to actually work. Implements Laravel's internal token creation mechanisms rather than trying to do it custom.
|
||||
* Updates some eggs to ensure they have the correct data and will continue working down the road. Fixes autoupdating on some source servers and MC related download links.
|
||||
* Emails should send properly now when a server is marked as installed to let the owner know it is ready for action.
|
||||
* Cancelling a file manager operation should cancel correctly across all browsers now.
|
||||
|
||||
### Changed
|
||||
* Attempting to upload a folder via the web file manager will now display a warning telling the user to use SFTP.
|
||||
* Changing your account password will now log out all other sessions that currently exist for that user.
|
||||
* Subusers with no permissions selected can be created.
|
||||
|
||||
## v0.7.7 (Derelict Dermodactylus)
|
||||
### Fixed
|
||||
* Fixes an issue with the sidebar logo not working correctly in some browsers due to the CSS being assigned.
|
||||
|
|
15
app/Contracts/Core/ReceivesEvents.php
Normal file
15
app/Contracts/Core/ReceivesEvents.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Contracts\Core;
|
||||
|
||||
use Pterodactyl\Events\Event;
|
||||
|
||||
interface ReceivesEvents
|
||||
{
|
||||
/**
|
||||
* Handles receiving an event from the application.
|
||||
*
|
||||
* @param \Pterodactyl\Events\Event $notification
|
||||
*/
|
||||
public function handle(Event $notification): void;
|
||||
}
|
|
@ -21,6 +21,14 @@ interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterfa
|
|||
*/
|
||||
public function getUsageStats(Node $node): array;
|
||||
|
||||
/**
|
||||
* Return the usage stats for a single node.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Node $node
|
||||
* @return array
|
||||
*/
|
||||
public function getUsageStatsRaw(Node $node): array;
|
||||
|
||||
/**
|
||||
* Return all available nodes with a searchable interface.
|
||||
*
|
||||
|
|
|
@ -200,4 +200,11 @@ interface RepositoryInterface
|
|||
* @return bool
|
||||
*/
|
||||
public function insertIgnore(array $values): bool;
|
||||
|
||||
/**
|
||||
* Get the amount of entries in the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int;
|
||||
}
|
||||
|
|
|
@ -145,4 +145,11 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
|
|||
* @return bool
|
||||
*/
|
||||
public function isUniqueUuidCombo(string $uuid, string $short): bool;
|
||||
|
||||
/**
|
||||
* Get the amount of servers that are suspended.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSuspendedServersCount(): int;
|
||||
}
|
||||
|
|
27
app/Events/Server/Installed.php
Normal file
27
app/Events/Server/Installed.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Events\Server;
|
||||
|
||||
use Pterodactyl\Events\Event;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Installed extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Models\Server
|
||||
*/
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @var \Pterodactyl\Models\Server
|
||||
*/
|
||||
public function __construct(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
}
|
||||
}
|
97
app/Http/Controllers/Admin/StatisticsController.php
Normal file
97
app/Http/Controllers/Admin/StatisticsController.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin;
|
||||
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Traits\Controllers\PlainJavascriptInjection;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
||||
|
||||
class StatisticsController extends Controller
|
||||
{
|
||||
use PlainJavascriptInjection;
|
||||
|
||||
private $allocationRepository;
|
||||
|
||||
private $databaseRepository;
|
||||
|
||||
private $eggRepository;
|
||||
|
||||
private $nodeRepository;
|
||||
|
||||
private $serverRepository;
|
||||
|
||||
private $userRepository;
|
||||
|
||||
public function __construct(
|
||||
AllocationRepositoryInterface $allocationRepository,
|
||||
DatabaseRepositoryInterface $databaseRepository,
|
||||
EggRepositoryInterface $eggRepository,
|
||||
NodeRepositoryInterface $nodeRepository,
|
||||
ServerRepositoryInterface $serverRepository,
|
||||
UserRepositoryInterface $userRepository
|
||||
) {
|
||||
$this->allocationRepository = $allocationRepository;
|
||||
$this->databaseRepository = $databaseRepository;
|
||||
$this->eggRepository = $eggRepository;
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->serverRepository = $serverRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$servers = $this->serverRepository->all();
|
||||
$nodes = $this->nodeRepository->all();
|
||||
$usersCount = $this->userRepository->count();
|
||||
$eggsCount = $this->eggRepository->count();
|
||||
$databasesCount = $this->databaseRepository->count();
|
||||
$totalAllocations = $this->allocationRepository->count();
|
||||
$suspendedServersCount = $this->serverRepository->getSuspendedServersCount();
|
||||
|
||||
$totalServerRam = 0;
|
||||
$totalNodeRam = 0;
|
||||
$totalServerDisk = 0;
|
||||
$totalNodeDisk = 0;
|
||||
foreach ($nodes as $node) {
|
||||
$stats = $this->nodeRepository->getUsageStatsRaw($node);
|
||||
$totalServerRam += $stats['memory']['value'];
|
||||
$totalNodeRam += $stats['memory']['max'];
|
||||
$totalServerDisk += $stats['disk']['value'];
|
||||
$totalNodeDisk += $stats['disk']['max'];
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
foreach ($nodes as $node) {
|
||||
$tokens[$node->id] = $node->daemonSecret;
|
||||
}
|
||||
|
||||
$this->injectJavascript([
|
||||
'servers' => $servers,
|
||||
'suspendedServers' => $suspendedServersCount,
|
||||
'totalServerRam' => $totalServerRam,
|
||||
'totalNodeRam' => $totalNodeRam,
|
||||
'totalServerDisk' => $totalServerDisk,
|
||||
'totalNodeDisk' => $totalNodeDisk,
|
||||
'nodes' => $nodes,
|
||||
'tokens' => $tokens,
|
||||
]);
|
||||
|
||||
return view('admin.statistics', [
|
||||
'servers' => $servers,
|
||||
'nodes' => $nodes,
|
||||
'usersCount' => $usersCount,
|
||||
'eggsCount' => $eggsCount,
|
||||
'totalServerRam' => $totalServerRam,
|
||||
'databasesCount' => $databasesCount,
|
||||
'totalNodeRam' => $totalNodeRam,
|
||||
'totalNodeDisk' => $totalNodeDisk,
|
||||
'totalServerDisk' => $totalServerDisk,
|
||||
'totalAllocations' => $totalAllocations,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,9 @@
|
|||
namespace Pterodactyl\Http\Controllers\Base;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Users\UserUpdateService;
|
||||
use Pterodactyl\Http\Requests\Base\AccountDataFormRequest;
|
||||
|
@ -15,6 +17,11 @@ class AccountController extends Controller
|
|||
*/
|
||||
protected $alert;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Auth\SessionGuard
|
||||
*/
|
||||
protected $sessionGuard;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\UserUpdateService
|
||||
*/
|
||||
|
@ -24,12 +31,14 @@ class AccountController extends Controller
|
|||
* AccountController constructor.
|
||||
*
|
||||
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||
* @param \Illuminate\Auth\AuthManager $authManager
|
||||
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
|
||||
*/
|
||||
public function __construct(AlertsMessageBag $alert, UserUpdateService $updateService)
|
||||
public function __construct(AlertsMessageBag $alert, AuthManager $authManager, UserUpdateService $updateService)
|
||||
{
|
||||
$this->alert = $alert;
|
||||
$this->updateService = $updateService;
|
||||
$this->sessionGuard = $authManager->guard();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,21 +59,26 @@ class AccountController extends Controller
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function update(AccountDataFormRequest $request)
|
||||
{
|
||||
$data = [];
|
||||
// Prevent logging this specific session out when the password is changed. This will
|
||||
// automatically update the user's password anyways, so no need to do anything else here.
|
||||
if ($request->input('do_action') === 'password') {
|
||||
$data['password'] = $request->input('new_password');
|
||||
} elseif ($request->input('do_action') === 'email') {
|
||||
$data['email'] = $request->input('new_email');
|
||||
} elseif ($request->input('do_action') === 'identity') {
|
||||
$data = $request->only(['name_first', 'name_last', 'username']);
|
||||
$this->sessionGuard->logoutOtherDevices($request->input('new_password'));
|
||||
} else {
|
||||
if ($request->input('do_action') === 'email') {
|
||||
$data = ['email' => $request->input('new_email')];
|
||||
} elseif ($request->input('do_action') === 'identity') {
|
||||
$data = $request->only(['name_first', 'name_last', 'username']);
|
||||
} else {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$this->updateService->setUserLevel(User::USER_LEVEL_USER);
|
||||
$this->updateService->handle($request->user(), $data);
|
||||
}
|
||||
|
||||
$this->updateService->setUserLevel(User::USER_LEVEL_USER);
|
||||
$this->updateService->handle($request->user(), $data);
|
||||
$this->alert->success(trans('base.account.details_updated'))->flash();
|
||||
|
||||
return redirect()->route('account');
|
||||
|
|
|
@ -7,9 +7,26 @@ use Illuminate\Http\Request;
|
|||
use Pterodactyl\Models\Node;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Events\Server\Installed as ServerInstalled;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
|
||||
class ActionController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
/**
|
||||
* ActionController constructor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $eventDispatcher
|
||||
*/
|
||||
public function __construct(EventDispatcher $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles install toggle request from daemon.
|
||||
*
|
||||
|
@ -37,6 +54,11 @@ class ActionController extends Controller
|
|||
$server->installed = ($status === 'installed') ? 1 : 2;
|
||||
$server->save();
|
||||
|
||||
// Only fire event if server installed successfully.
|
||||
if ($server->installed === 1) {
|
||||
$this->eventDispatcher->dispatch(new ServerInstalled($server));
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,6 @@ class SubuserController extends Controller
|
|||
* @return \Illuminate\Http\RedirectResponse
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
|
||||
|
@ -171,7 +170,7 @@ class SubuserController extends Controller
|
|||
$this->alert->success(trans('server.users.user_assigned'))->flash();
|
||||
|
||||
return redirect()->route('server.subusers.view', [
|
||||
'uuid' => $server->uuid,
|
||||
'uuid' => $server->uuidShort,
|
||||
'id' => $subuser->hashid,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ use Pterodactyl\Http\Middleware\Api\AuthenticateKey;
|
|||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Pterodactyl\Http\Middleware\AccessingValidServer;
|
||||
use Pterodactyl\Http\Middleware\Api\SetSessionDriver;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Pterodactyl\Http\Middleware\MaintenanceMiddleware;
|
||||
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess;
|
||||
|
@ -63,6 +65,7 @@ class Kernel extends HttpKernel
|
|||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
|
@ -108,6 +111,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)
|
||||
//
|
||||
|
|
44
app/Http/Middleware/MaintenanceMiddleware.php
Normal file
44
app/Http/Middleware/MaintenanceMiddleware.php
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ class StoreNodeRequest extends ApplicationApiRequest
|
|||
$response = parent::validated();
|
||||
$response['daemonListen'] = $response['daemon_listen'];
|
||||
$response['daemonSFTP'] = $response['daemon_sftp'];
|
||||
$response['daemonBase'] = $response['daemon_base'];
|
||||
$response['daemonBase'] = $response['daemon_base'] ?? (new Node)->getAttribute('daemonBase');
|
||||
|
||||
unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class SubuserStoreFormRequest extends ServerFormRequest
|
|||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'permissions' => 'present|array',
|
||||
'permissions' => 'sometimes|array',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,7 @@ class AccountCreated extends Notification implements ShouldQueue
|
|||
/**
|
||||
* The user model for the created user.
|
||||
*
|
||||
* @var object
|
||||
* @var \Pterodactyl\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
|
@ -65,7 +65,7 @@ class AccountCreated extends Notification implements ShouldQueue
|
|||
->line('Email: ' . $this->user->email);
|
||||
|
||||
if (! is_null($this->token)) {
|
||||
return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . $this->user->email));
|
||||
return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . urlencode($this->user->email)));
|
||||
}
|
||||
|
||||
return $message;
|
||||
|
|
69
app/Notifications/ServerInstalled.php
Normal file
69
app/Notifications/ServerInstalled.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Pterodactyl\Events\Event;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Pterodactyl\Contracts\Core\ReceivesEvents;
|
||||
use Illuminate\Contracts\Notifications\Dispatcher;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ServerInstalled extends Notification implements ShouldQueue, ReceivesEvents
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Models\Server
|
||||
*/
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Handle a direct call to this notification from the server installed event. This is configured
|
||||
* in the event service provider.
|
||||
*
|
||||
* @param \Pterodactyl\Events\Event|\Pterodactyl\Events\Server\Installed $event
|
||||
*/
|
||||
public function handle(Event $event): void
|
||||
{
|
||||
$event->server->loadMissing('user');
|
||||
|
||||
$this->server = $event->server;
|
||||
$this->user = $event->server->user;
|
||||
|
||||
// Since we are calling this notification directly from an event listener we need to fire off the dispatcher
|
||||
// to send the email now. Don't use send() or you'll end up firing off two different events.
|
||||
Container::getInstance()->make(Dispatcher::class)->sendNow($this->user, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail()
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hello ' . $this->user->username . '.')
|
||||
->line('Your server has finished installing and is now ready for you to use.')
|
||||
->line('Server Name: ' . $this->server->name)
|
||||
->action('Login and Begin Using', route('index'));
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Pterodactyl\Events\Server\Installed as ServerInstalledEvent;
|
||||
use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
|
@ -11,5 +13,9 @@ class EventServiceProvider extends ServiceProvider
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [];
|
||||
protected $listen = [
|
||||
ServerInstalledEvent::class => [
|
||||
ServerInstalledNotification::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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'));
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ abstract class BaseRepository implements BaseRepositoryInterface
|
|||
$headers['X-Access-Token'] = $this->getToken() ?? $this->getNode()->daemonSecret;
|
||||
|
||||
return new Client([
|
||||
'verify' => config('app.env') === 'production',
|
||||
'base_uri' => sprintf('%s://%s:%s/v1/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen),
|
||||
'timeout' => config('pterodactyl.guzzle.timeout'),
|
||||
'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'),
|
||||
|
|
|
@ -296,4 +296,14 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
|
||||
return $this->getBuilder()->getConnection()->statement($statement, $bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of entries in the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->getBuilder()->count();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,33 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the usage stats for a single node.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Node $node
|
||||
* @return array
|
||||
*/
|
||||
public function getUsageStatsRaw(Node $node): array
|
||||
{
|
||||
$stats = $this->getBuilder()->select(
|
||||
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
|
||||
|
||||
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
|
||||
$maxUsage = $node->{$key};
|
||||
if ($node->{$key . '_overallocate'} > 0) {
|
||||
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
|
||||
}
|
||||
|
||||
return [
|
||||
$key => [
|
||||
'value' => $value,
|
||||
'max' => $maxUsage,
|
||||
],
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all available nodes with a searchable interface.
|
||||
*
|
||||
|
|
|
@ -328,4 +328,14 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
|||
$this->app->make(SubuserRepository::class)->getBuilder()->select('server_id')->where('user_id', $user)
|
||||
)->pluck('id')->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of servers that are suspended.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSuspendedServersCount(): int
|
||||
{
|
||||
return $this->getBuilder()->where('suspended', true)->count();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Services\Helpers;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
|
||||
class TemporaryPasswordService
|
||||
{
|
||||
const HMAC_ALGO = 'sha256';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Hashing\Hasher
|
||||
*/
|
||||
protected $hasher;
|
||||
|
||||
/**
|
||||
* TemporaryPasswordService constructor.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
|
||||
*/
|
||||
public function __construct(ConnectionInterface $connection, Hasher $hasher)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->hasher = $hasher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a password reset token for a specific email address.
|
||||
*
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
public function handle($email)
|
||||
{
|
||||
$token = hash_hmac(self::HMAC_ALGO, Uuid::uuid4()->toString(), config('app.key'));
|
||||
|
||||
$this->connection->table('password_resets')->insert([
|
||||
'email' => $email,
|
||||
'token' => $this->hasher->make($token),
|
||||
]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ class EnvironmentService
|
|||
}
|
||||
|
||||
// Process variables set in the configuration file.
|
||||
foreach ($this->config->get('pterodactyl.environment_mappings', []) as $key => $object) {
|
||||
foreach ($this->config->get('pterodactyl.environment_variables', []) as $key => $object) {
|
||||
if (is_callable($object)) {
|
||||
$variables[$key] = call_user_func($object, $server);
|
||||
} else {
|
||||
|
|
|
@ -56,6 +56,8 @@ class PermissionCreationService
|
|||
}
|
||||
}
|
||||
|
||||
$this->repository->withoutFreshModel()->insert($insertPermissions);
|
||||
if (! empty($insertPermissions)) {
|
||||
$this->repository->withoutFreshModel()->insert($insertPermissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ namespace Pterodactyl\Services\Users;
|
|||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Contracts\Auth\PasswordBroker;
|
||||
use Pterodactyl\Notifications\AccountCreated;
|
||||
use Pterodactyl\Services\Helpers\TemporaryPasswordService;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
|
||||
class UserCreationService
|
||||
|
@ -22,9 +22,9 @@ class UserCreationService
|
|||
private $hasher;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Helpers\TemporaryPasswordService
|
||||
* @var \Illuminate\Contracts\Auth\PasswordBroker
|
||||
*/
|
||||
private $passwordService;
|
||||
private $passwordBroker;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
|
@ -36,18 +36,18 @@ class UserCreationService
|
|||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
|
||||
* @param \Pterodactyl\Services\Helpers\TemporaryPasswordService $passwordService
|
||||
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwordBroker
|
||||
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
Hasher $hasher,
|
||||
TemporaryPasswordService $passwordService,
|
||||
PasswordBroker $passwordBroker,
|
||||
UserRepositoryInterface $repository
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->hasher = $hasher;
|
||||
$this->passwordService = $passwordService;
|
||||
$this->passwordBroker = $passwordBroker;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
@ -68,8 +68,8 @@ class UserCreationService
|
|||
|
||||
$this->connection->beginTransaction();
|
||||
if (! isset($data['password']) || empty($data['password'])) {
|
||||
$generateResetToken = true;
|
||||
$data['password'] = $this->hasher->make(str_random(30));
|
||||
$token = $this->passwordService->handle($data['email']);
|
||||
}
|
||||
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
|
@ -77,6 +77,10 @@ class UserCreationService
|
|||
'uuid' => Uuid::uuid4()->toString(),
|
||||
]), true, true);
|
||||
|
||||
if (isset($generateResetToken)) {
|
||||
$token = $this->passwordBroker->createToken($user);
|
||||
}
|
||||
|
||||
$this->connection->commit();
|
||||
$user->notify(new AccountCreated($user, $token ?? null));
|
||||
|
||||
|
|
22
app/Traits/Controllers/PlainJavascriptInjection.php
Normal file
22
app/Traits/Controllers/PlainJavascriptInjection.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Stan
|
||||
* Date: 26-5-2018
|
||||
* Time: 20:56.
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Traits\Controllers;
|
||||
|
||||
use JavaScript;
|
||||
|
||||
trait PlainJavascriptInjection
|
||||
{
|
||||
/**
|
||||
* Injects statistics into javascript.
|
||||
*/
|
||||
public function injectJavascript($data)
|
||||
{
|
||||
Javascript::put($data);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 35
|
||||
threshold: 1
|
||||
project: off
|
||||
patch: off
|
||||
comment:
|
||||
layout: "diff"
|
||||
|
|
561
composer.lock
generated
561
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,7 @@ return [
|
|||
| change this value if you are not maintaining your own internal versions.
|
||||
*/
|
||||
|
||||
'version' => '0.7.7',
|
||||
'version' => '0.7.8',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -34,6 +34,7 @@ return [
|
|||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'unix_socket' => env('DB_SOCKET'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'panel'),
|
||||
'username' => env('DB_USERNAME', 'pterodactyl'),
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
"meta": {
|
||||
"version": "PTDL_v1"
|
||||
},
|
||||
"exported_at": "2018-02-27T00:57:04-06:00",
|
||||
"exported_at": "2018-06-25T15:47:07-04:00",
|
||||
"name": "Forge Minecraft",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.",
|
||||
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"installation": {
|
||||
"script": "#!\/bin\/ash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\nGET_VERSIONS=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9]]\\?\\.\\?[0-9]\\?[0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\?[0-9]\\.[0-9][0-9][0-9][0-9]')\r\nLATEST_VERSION=$(echo $GET_VERSIONS | sed 's\/ \/\/g')\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-installer.jar -o installer.jar\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-universal.jar -o server.jar\r\n\r\njava -jar installer.jar --installServer\r\nrm -rf installer.jar",
|
||||
"script": "#!\/bin\/ash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\nif [ -z \"$MC_VERSION\" ] || [ \"$MC_VERSION\" == \"latest\" ]; then\r\n FORGE_VERSION=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9]]\\?\\.\\?[0-9]\\?[0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\?[0-9]\\.[0-9][0-9][0-9][0-9]' | sed 's\/ \/\/g')\r\nelse\r\n FORGE_VERSION=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/index_$MC_VERSION.html | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9]]\\?\\.\\?[0-9]\\?[0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\?[0-9]\\.[0-9][0-9][0-9][0-9]' | sed 's\/ \/\/g')\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"\\nDownloading Forge Version $FORGE_VERSION\\n\"\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$FORGE_VERSION\/forge-$FORGE_VERSION-installer.jar -o installer.jar\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$FORGE_VERSION\/forge-$FORGE_VERSION-universal.jar -o $SERVER_JARFILE\r\n\r\necho -e \"\\nInstalling forge server usint the installer jar file.\\n\"\r\njava -jar installer.jar --installServer\r\n\r\necho -e \"\\nDeleting installer jar file and cleaning up.\\n\"\r\nrm -rf installer.jar",
|
||||
"container": "frolvlad\/alpine-oraclejdk8:cleaned",
|
||||
"entrypoint": "ash"
|
||||
}
|
||||
|
@ -31,6 +31,15 @@
|
|||
"user_viewable": 1,
|
||||
"user_editable": 1,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/"
|
||||
},
|
||||
{
|
||||
"name": "Minecraft version",
|
||||
"description": "The version of minecraft that you want to run. Example (1.10.2).",
|
||||
"env_variable": "MC_VERSION",
|
||||
"default_value": "latest",
|
||||
"user_viewable": 1,
|
||||
"user_editable": 1,
|
||||
"rules": "required|string|max:20"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"meta": {
|
||||
"version": "PTDL_v1"
|
||||
},
|
||||
"exported_at": "2017-11-03T22:15:07-05:00",
|
||||
"exported_at": "2018-06-19T17:09:18-04:00",
|
||||
"name": "Vanilla Minecraft",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.",
|
||||
|
@ -17,8 +17,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"installation": {
|
||||
"script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl -s https:\/\/s3.amazonaws.com\/Minecraft.Download\/versions\/versions.json | grep -o \"[[:digit:]]\\.[0-9]*\\.[0-9]\" | head -n 1`\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n DL_VERSION=$LATEST_VERSION\r\nelse\r\n DL_VERSION=$VANILLA_VERSION\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/s3.amazonaws.com\/Minecraft.Download\/versions\/${DL_VERSION}\/minecraft_server.${DL_VERSION}.jar",
|
||||
"container": "alpine:3.4",
|
||||
"script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl jq\r\n\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n MANIFEST_URL=$(curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq .versions | jq -r '.[] | select(.id == \"'$LATEST_VERSION'\") | .url')\r\nelse\r\n MANIFEST_URL=$(curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq .versions | jq -r '.[] | select(.id == \"'$VANILLA_VERSION'\") | .url')\r\nfi\r\n\r\nDOWNLOAD_URL=`curl $MANIFEST_URL | jq .downloads.server | jq -r '. | .url'`\r\n\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL",
|
||||
"container": "alpine:3.7",
|
||||
"entrypoint": "ash"
|
||||
}
|
||||
},
|
||||
|
@ -39,7 +39,7 @@
|
|||
"default_value": "latest",
|
||||
"user_viewable": 1,
|
||||
"user_editable": 1,
|
||||
"rules": "required|string|between:3,7"
|
||||
"rules": "required|string|between:3,15"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"meta": {
|
||||
"version": "PTDL_v1"
|
||||
},
|
||||
"exported_at": "2018-01-21T16:59:47-06:00",
|
||||
"exported_at": "2018-06-19T07:46:06-04:00",
|
||||
"name": "Counter-Strike: Global Offensive",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.",
|
||||
|
@ -11,7 +11,7 @@
|
|||
"startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
||||
"config": {
|
||||
"files": "{}",
|
||||
"startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}",
|
||||
"startup": "{\r\n \"done\": \"Connection to Steam servers successful\",\r\n \"userInteraction\": []\r\n}",
|
||||
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}",
|
||||
"stop": "quit"
|
||||
},
|
||||
|
@ -40,6 +40,15 @@
|
|||
"user_viewable": 1,
|
||||
"user_editable": 1,
|
||||
"rules": "required|string|alpha_num|size:32"
|
||||
},
|
||||
{
|
||||
"name": "Source AppID",
|
||||
"description": "Required for game to update on server restart. Do not modify this.",
|
||||
"env_variable": "SRCDS_APPID",
|
||||
"default_value": "740",
|
||||
"user_viewable": 0,
|
||||
"user_editable": 0,
|
||||
"rules": "required|string|max:20"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"meta": {
|
||||
"version": "PTDL_v1"
|
||||
},
|
||||
"exported_at": "2018-01-21T16:59:47-06:00",
|
||||
"exported_at": "2018-06-19T07:46:27-04:00",
|
||||
"name": "Garrys Mod",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.",
|
||||
|
@ -40,6 +40,15 @@
|
|||
"user_viewable": 1,
|
||||
"user_editable": 1,
|
||||
"rules": "required|string|alpha_num|size:32"
|
||||
},
|
||||
{
|
||||
"name": "Source AppID",
|
||||
"description": "Required for game to update on server restart. Do not modify this.",
|
||||
"env_variable": "SRCDS_APPID",
|
||||
"default_value": "4020",
|
||||
"user_viewable": 0,
|
||||
"user_editable": 0,
|
||||
"rules": "required|string|max:20"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -473,3 +473,12 @@ label.control-label > span.field-optional:before {
|
|||
height: 42px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.search01 {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.number-info-box-content {
|
||||
padding: 15px 10px 0;
|
||||
}
|
||||
|
||||
|
|
118
public/themes/pterodactyl/js/admin/statistics.js
Normal file
118
public/themes/pterodactyl/js/admin/statistics.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
var freeDisk = Pterodactyl.totalNodeDisk - Pterodactyl.totalServerDisk;
|
||||
let diskChart = new Chart($('#disk_chart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Free Disk', 'Used Disk'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Disk (in MB)',
|
||||
backgroundColor: ['#51B060', '#ff0000'],
|
||||
data: [freeDisk, Pterodactyl.totalServerDisk]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var freeRam = Pterodactyl.totalNodeRam - Pterodactyl.totalServerRam;
|
||||
let ramChart = new Chart($('#ram_chart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Free RAM', 'Used RAM'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Memory (in MB)',
|
||||
backgroundColor: ['#51B060', '#ff0000'],
|
||||
data: [freeRam, Pterodactyl.totalServerRam]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var activeServers = Pterodactyl.servers.length - Pterodactyl.suspendedServers;
|
||||
let serversChart = new Chart($('#servers_chart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Active', 'Suspended'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Servers',
|
||||
backgroundColor: ['#51B060', '#E08E0B'],
|
||||
data: [activeServers, Pterodactyl.suspendedServers]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
let statusChart = new Chart($('#status_chart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Online', 'Offline', 'Installing', 'Error'],
|
||||
datasets: [
|
||||
{
|
||||
label: '',
|
||||
backgroundColor: ['#51B060', '#b7b7b7', '#E08E0B', '#ff0000'],
|
||||
data: [0,0,0,0]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
var servers = Pterodactyl.servers;
|
||||
var nodes = Pterodactyl.nodes;
|
||||
|
||||
for (let i = 0; i < servers.length; i++) {
|
||||
setTimeout(getStatus, 200 * i, servers[i]);
|
||||
}
|
||||
|
||||
function getStatus(server) {
|
||||
var uuid = server.uuid;
|
||||
var node = getNodeByID(server.node_id);
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: node.scheme + '://' + node.fqdn + ':'+node.daemonListen+'/v1/server',
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'X-Access-Server': uuid,
|
||||
'X-Access-Token': Pterodactyl.tokens[node.id],
|
||||
}
|
||||
}).done(function (data) {
|
||||
|
||||
if (typeof data.status === 'undefined') {
|
||||
// Error
|
||||
statusChart.data.datasets[0].data[3]++;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.status) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 30:
|
||||
// Offline
|
||||
statusChart.data.datasets[0].data[1]++;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
// Online
|
||||
statusChart.data.datasets[0].data[0]++;
|
||||
break;
|
||||
case 20:
|
||||
// Installing
|
||||
statusChart.data.datasets[0].data[2]++;
|
||||
break;
|
||||
}
|
||||
statusChart.update();
|
||||
}).fail(function (jqXHR) {
|
||||
// Error
|
||||
statusChart.data.datasets[0].data[3]++;
|
||||
statusChart.update();
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeByID(id) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].id === id) {
|
||||
return nodes[i];
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -55,6 +55,10 @@ class ActionsClass {
|
|||
showLoaderOnConfirm: true,
|
||||
inputValue: inputValue
|
||||
}, (val) => {
|
||||
if (val === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
headers: {
|
||||
|
@ -100,6 +104,10 @@ class ActionsClass {
|
|||
showLoaderOnConfirm: true,
|
||||
inputValue: `${currentPath}${currentName}`,
|
||||
}, (val) => {
|
||||
if (val === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
headers: {
|
||||
|
@ -233,6 +241,10 @@ class ActionsClass {
|
|||
showLoaderOnConfirm: true,
|
||||
inputValue: `${currentPath}${currentName}`,
|
||||
}, (val) => {
|
||||
if (val === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
headers: {
|
||||
|
|
|
@ -61,6 +61,18 @@
|
|||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
window.foldersDetectedInDrag = function (event) {
|
||||
var folderDetected = false;
|
||||
var files = event.dataTransfer.files;
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
if (!f.type && f.size === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return folderDetected;
|
||||
};
|
||||
|
||||
var dropCounter = 0;
|
||||
$('#load_files').bind({
|
||||
dragenter: function (event) {
|
||||
|
@ -75,6 +87,15 @@
|
|||
}
|
||||
},
|
||||
drop: function (event) {
|
||||
if (window.foldersDetectedInDrag(event.originalEvent)) {
|
||||
$.notify({
|
||||
message: 'Folder uploads are not supported. Please use SFTP to upload whole directories.',
|
||||
}, {
|
||||
type: 'warning',
|
||||
delay: 0
|
||||
});
|
||||
}
|
||||
|
||||
dropCounter = 0;
|
||||
$(this).removeClass('hasFileHover');
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -74,6 +74,7 @@ return [
|
|||
'tasks' => 'Tasks',
|
||||
'seconds' => 'Seconds',
|
||||
'minutes' => 'Minutes',
|
||||
'under_maintenance' => 'Under Maintenance',
|
||||
'days' => [
|
||||
'sun' => 'Sunday',
|
||||
'mon' => 'Monday',
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
@foreach ($nodes as $node)
|
||||
<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><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->memory }} MB</td>
|
||||
<td>{{ $node->disk }} MB</td>
|
||||
|
|
|
@ -96,6 +96,17 @@
|
|||
</div>
|
||||
<div class="box-body">
|
||||
<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="info-box bg-{{ $stats['disk']['css'] }}">
|
||||
<span class="info-box-icon"><i class="ion ion-ios-folder-outline"></i></span>
|
||||
|
|
|
@ -108,6 +108,20 @@
|
|||
</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>
|
||||
</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>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<h3 class="box-title">Force Delete Server</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>This action will attempt to delete the server from both the panel and daemon. The the daemon does not respond, or reports an error the deletion will continue.</p>
|
||||
<p>This action will attempt to delete the server from both the panel and daemon. If the daemon does not respond, or reports an error the deletion will continue.</p>
|
||||
<p class="text-danger small">Deleting a server is an irreversible action. <strong>All server data</strong> (including files and users) will be removed from the system. This method may leave dangling files on your daemon if it reports an error.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
|
|
141
resources/themes/pterodactyl/admin/statistics.blade.php
Normal file
141
resources/themes/pterodactyl/admin/statistics.blade.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
@extends('layouts.admin')
|
||||
@include('partials/admin.settings.nav', ['activeTab' => 'basic'])
|
||||
|
||||
@section('title')
|
||||
Statistics Overview
|
||||
@endsection
|
||||
|
||||
@section('content-header')
|
||||
<h1>Statistics Overview<small>Monitor your panel usage.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Statistics</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
Servers
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<canvas id="servers_chart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<canvas id="status_chart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-server"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Servers</span>
|
||||
<span class="info-box-number">{{ count($servers) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="ion ion-ios-barcode-outline"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total used Memory (in MB)</span>
|
||||
<span class="info-box-number">{{ $totalServerRam }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="ion ion-stats-bars"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total used Disk (in MB)</span>
|
||||
<span class="info-box-number">{{ $totalServerDisk }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
Nodes
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<canvas id="ram_chart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<canvas id="disk_chart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="ion ion-ios-barcode-outline"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total RAM</span>
|
||||
<span class="info-box-number">{{ $totalNodeRam }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="ion ion-stats-bars"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Disk Space</span>
|
||||
<span class="info-box-number">{{ $totalNodeDisk }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-location-arrow"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Allocations</span>
|
||||
<span class="info-box-number">{{ $totalAllocations }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-3">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-gamepad"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Eggs</span>
|
||||
<span class="info-box-number">{{ $eggsCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-3">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-users"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Users</span>
|
||||
<span class="info-box-number">{{ $usersCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-3">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-server"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Nodes</span>
|
||||
<span class="info-box-number">{{ count($nodes) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-3">
|
||||
<div class="info-box bg-blue">
|
||||
<span class="info-box-icon"><i class="fa fa-database"></i></span>
|
||||
<div class="info-box-content number-info-box-content">
|
||||
<span class="info-box-text">Total Databases</span>
|
||||
<span class="info-box-number">{{ $databasesCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
{!! Theme::js('vendor/chartjs/chart.min.js') !!}
|
||||
{!! Theme::js('js/admin/statistics.js') !!}
|
||||
@endsection
|
|
@ -23,10 +23,10 @@
|
|||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">@lang('base.index.list')</h3>
|
||||
<div class="box-tools">
|
||||
<div class="box-tools search01">
|
||||
<form action="{{ route('index') }}" method="GET">
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" name="query" class="form-control pull-right" style="width:30%;" value="{{ request()->input('query') }}" placeholder="@lang('strings.search')">
|
||||
<input type="text" name="query" class="form-control pull-right" value="{{ request()->input('query') }}" placeholder="@lang('strings.search')">
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
</div>
|
||||
|
@ -64,9 +64,15 @@
|
|||
<span class="label bg-blue">@lang('strings.subuser')</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center" data-action="status">
|
||||
<span class="label label-default"><i class="fa fa-refresh fa-fw fa-spin"></i></span>
|
||||
</td>
|
||||
@if($server->node->maintenance_mode)
|
||||
<td class="text-center">
|
||||
<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>
|
||||
@if (! empty($server->description))
|
||||
<tr class="server-description">
|
||||
|
|
30
resources/themes/pterodactyl/errors/maintenance.blade.php
Normal file
30
resources/themes/pterodactyl/errors/maintenance.blade.php
Normal file
|
@ -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
|
|
@ -80,6 +80,11 @@
|
|||
<i class="fa fa-home"></i> <span>Overview</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ Route::currentRouteName() !== 'admin.statistics' ?: 'active' }}">
|
||||
<a href="{{ route('admin.statistics') }}">
|
||||
<i class="fa fa-tachometer"></i> <span>Statistics</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.settings') ?: 'active' }}">
|
||||
<a href="{{ route('admin.settings')}}">
|
||||
<i class="fa fa-wrench"></i> <span>Settings</span>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<header class="main-header">
|
||||
<a href="{{ route('index') }}" class="logo">
|
||||
<span class="logo-lg">{{ config('app.name', 'Pterodactyl') }}</span>
|
||||
<span class="logo-mini"><img src="favicons/android-chrome-192x192.png"></span>
|
||||
<span class="logo-mini"><img src="/favicons/android-chrome-192x192.png"></span>
|
||||
</a>
|
||||
<nav class="navbar navbar-static-top">
|
||||
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
|
||||
|
@ -146,7 +146,7 @@
|
|||
</a>
|
||||
</li>
|
||||
@endcan
|
||||
@can('list-tasks', $server)
|
||||
@can('list-schedules', $server)
|
||||
<li
|
||||
@if(starts_with(Route::currentRouteName(), 'server.schedules'))
|
||||
class="active"
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
<script>
|
||||
$(document).ready(function () {
|
||||
Editor.setValue($('#editorSetContent').val(), -1);
|
||||
Editor.getSession().setUndoManager(new ace.UndoManager());
|
||||
$('#editorLoadingOverlay').hide();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
{!! Theme::js('vendor/lodash/lodash.js') !!}
|
||||
{!! Theme::js('vendor/siofu/client.min.js') !!}
|
||||
@if(App::environment('production'))
|
||||
{!! Theme::js('js/frontend/files/filemanager.min.js') !!}
|
||||
{!! Theme::js('js/frontend/files/filemanager.min.js?updated-cancel-buttons') !!}
|
||||
@else
|
||||
{!! Theme::js('js/frontend/files/src/index.js') !!}
|
||||
{!! Theme::js('js/frontend/files/src/contextmenu.js') !!}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
Route::get('/', 'BaseController@index')->name('admin.index');
|
||||
Route::get('/statistics', 'StatisticsController@index')->name('admin.statistics');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
110
tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php
Normal file
110
tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Stan
|
||||
* Date: 26-5-2018
|
||||
* Time: 21:06.
|
||||
*/
|
||||
|
||||
namespace Tests\Unit\Http\Controllers\Admin;
|
||||
|
||||
use Mockery as m;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Tests\Assertions\ControllerAssertionsTrait;
|
||||
use Tests\Unit\Http\Controllers\ControllerTestCase;
|
||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Http\Controllers\Admin\StatisticsController;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
||||
|
||||
class StatisticsControllerTest extends ControllerTestCase
|
||||
{
|
||||
use ControllerAssertionsTrait;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $allocationRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $databaseRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $eggRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $nodeRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $serverRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $userRepository;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->allocationRepository = m::mock(AllocationRepositoryInterface::class);
|
||||
$this->databaseRepository = m::mock(DatabaseRepositoryInterface::class);
|
||||
$this->eggRepository = m::mock(EggRepositoryInterface::class);
|
||||
$this->nodeRepository = m::mock(NodeRepositoryInterface::class);
|
||||
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
|
||||
$this->userRepository = m::mock(UserRepositoryInterface::class);
|
||||
}
|
||||
|
||||
public function testIndexController()
|
||||
{
|
||||
$controller = $this->getController();
|
||||
|
||||
$this->serverRepository->shouldReceive('all')->withNoArgs();
|
||||
$this->nodeRepository->shouldReceive('all')->withNoArgs()->andReturn(collect([factory(Node::class)->make(), factory(Node::class)->make()]));
|
||||
$this->userRepository->shouldReceive('count')->withNoArgs();
|
||||
$this->eggRepository->shouldReceive('count')->withNoArgs();
|
||||
$this->databaseRepository->shouldReceive('count')->withNoArgs();
|
||||
$this->allocationRepository->shouldReceive('count')->withNoArgs();
|
||||
$this->serverRepository->shouldReceive('getSuspendedServersCount')->withNoArgs();
|
||||
|
||||
$this->nodeRepository->shouldReceive('getUsageStatsRaw')->twice()->andReturn([
|
||||
'memory' => [
|
||||
'value' => 1024,
|
||||
'max' => 512,
|
||||
],
|
||||
'disk' => [
|
||||
'value' => 1024,
|
||||
'max' => 512,
|
||||
],
|
||||
]);
|
||||
|
||||
$controller->shouldReceive('injectJavascript')->once();
|
||||
|
||||
$response = $controller->index();
|
||||
|
||||
$this->assertIsViewResponse($response);
|
||||
$this->assertViewNameEquals('admin.statistics', $response);
|
||||
}
|
||||
|
||||
private function getController()
|
||||
{
|
||||
return $this->buildMockedController(StatisticsController::class, [$this->allocationRepository,
|
||||
$this->databaseRepository,
|
||||
$this->eggRepository,
|
||||
$this->nodeRepository,
|
||||
$this->serverRepository,
|
||||
$this->userRepository, ]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ namespace Tests\Unit\Http\Controllers\Base;
|
|||
|
||||
use Mockery as m;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Auth\SessionGuard;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Services\Users\UserUpdateService;
|
||||
use Tests\Unit\Http\Controllers\ControllerTestCase;
|
||||
|
@ -17,6 +19,16 @@ class AccountControllerTest extends ControllerTestCase
|
|||
*/
|
||||
protected $alert;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Auth\AuthManager|\Mockery\Mock
|
||||
*/
|
||||
protected $authManager;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Auth\SessionGuard|\Mockery\Mock
|
||||
*/
|
||||
protected $sessionGuard;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\UserUpdateService|\Mockery\Mock
|
||||
*/
|
||||
|
@ -31,6 +43,10 @@ class AccountControllerTest extends ControllerTestCase
|
|||
|
||||
$this->alert = m::mock(AlertsMessageBag::class);
|
||||
$this->updateService = m::mock(UserUpdateService::class);
|
||||
$this->authManager = m::mock(AuthManager::class);
|
||||
$this->sessionGuard = m::mock(SessionGuard::class);
|
||||
|
||||
$this->authManager->shouldReceive('guard')->once()->andReturn($this->sessionGuard);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,13 +66,11 @@ class AccountControllerTest extends ControllerTestCase
|
|||
public function testUpdateControllerForPassword()
|
||||
{
|
||||
$this->setRequestMockClass(AccountDataFormRequest::class);
|
||||
$user = $this->generateRequestUserModel();
|
||||
|
||||
$this->request->shouldReceive('input')->with('do_action')->andReturn('password');
|
||||
$this->request->shouldReceive('input')->with('new_password')->once()->andReturn('test-password');
|
||||
$this->sessionGuard->shouldReceive('logoutOtherDevices')->once()->with('test-password')->andReturnSelf();
|
||||
|
||||
$this->updateService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull();
|
||||
$this->updateService->shouldReceive('handle')->with($user, ['password' => 'test-password'])->once()->andReturn(collect());
|
||||
$this->alert->shouldReceive('success->flash')->once()->andReturnNull();
|
||||
|
||||
$response = $this->getController()->update($this->request);
|
||||
|
@ -113,6 +127,6 @@ class AccountControllerTest extends ControllerTestCase
|
|||
*/
|
||||
private function getController(): AccountController
|
||||
{
|
||||
return new AccountController($this->alert, $this->updateService);
|
||||
return new AccountController($this->alert, $this->authManager, $this->updateService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ class SubuserControllerTest extends ControllerTestCase
|
|||
$response = $controller->store($this->request);
|
||||
$this->assertIsRedirectResponse($response);
|
||||
$this->assertRedirectRouteEquals('server.subusers.view', $response, [
|
||||
'uuid' => $server->uuid,
|
||||
'uuid' => $server->uuidShort,
|
||||
'id' => $subuser->hashid,
|
||||
]);
|
||||
}
|
||||
|
|
70
tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php
Normal file
70
tests/Unit/Http/Middleware/MaintenanceMiddlewareTest.php
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Helpers;
|
||||
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
use Tests\Traits\MocksUuids;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Services\Helpers\TemporaryPasswordService;
|
||||
|
||||
class TemporaryPasswordServiceTest extends TestCase
|
||||
{
|
||||
use MocksUuids;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Hashing\Hasher|\Mockery\Mock
|
||||
*/
|
||||
protected $hasher;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Helpers\TemporaryPasswordService
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->connection = m::mock(ConnectionInterface::class);
|
||||
$this->hasher = m::mock(Hasher::class);
|
||||
|
||||
$this->service = new TemporaryPasswordService($this->connection, $this->hasher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a temporary password is stored and the token is returned.
|
||||
*/
|
||||
public function testTemporaryPasswordIsStored()
|
||||
{
|
||||
$token = hash_hmac(TemporaryPasswordService::HMAC_ALGO, $this->getKnownUuid(), config('app.key'));
|
||||
|
||||
$this->hasher->shouldReceive('make')->with($token)->once()->andReturn('hashed_token');
|
||||
$this->connection->shouldReceive('table')->with('password_resets')->once()->andReturnSelf();
|
||||
$this->connection->shouldReceive('insert')->with([
|
||||
'email' => 'test@example.com',
|
||||
'token' => 'hashed_token',
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$response = $this->service->handle('test@example.com');
|
||||
$this->assertNotEmpty($response);
|
||||
$this->assertEquals($token, $response);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
|||
|
||||
class EnvironmentServiceTest extends TestCase
|
||||
{
|
||||
const CONFIG_MAPPING = 'pterodactyl.environment_mappings';
|
||||
const CONFIG_MAPPING = 'pterodactyl.environment_variables';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
|
||||
|
|
|
@ -9,9 +9,9 @@ use Tests\Traits\MocksUuids;
|
|||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Contracts\Auth\PasswordBroker;
|
||||
use Pterodactyl\Notifications\AccountCreated;
|
||||
use Pterodactyl\Services\Users\UserCreationService;
|
||||
use Pterodactyl\Services\Helpers\TemporaryPasswordService;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
|
||||
class UserCreationServiceTest extends TestCase
|
||||
|
@ -29,9 +29,9 @@ class UserCreationServiceTest extends TestCase
|
|||
private $hasher;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Helpers\TemporaryPasswordService|\Mockery\Mock
|
||||
* @var \Illuminate\Contracts\Auth\PasswordBroker|\Mockery\Mock
|
||||
*/
|
||||
private $passwordService;
|
||||
private $passwordBroker;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
|
||||
|
@ -48,7 +48,7 @@ class UserCreationServiceTest extends TestCase
|
|||
Notification::fake();
|
||||
$this->connection = m::mock(ConnectionInterface::class);
|
||||
$this->hasher = m::mock(Hasher::class);
|
||||
$this->passwordService = m::mock(TemporaryPasswordService::class);
|
||||
$this->passwordBroker = m::mock(PasswordBroker::class);
|
||||
$this->repository = m::mock(UserRepositoryInterface::class);
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ class UserCreationServiceTest extends TestCase
|
|||
$this->hasher->shouldNotReceive('make');
|
||||
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
|
||||
$this->hasher->shouldReceive('make')->once()->andReturn('created-enc-password');
|
||||
$this->passwordService->shouldReceive('handle')->with($user->email)->once()->andReturn('random-token');
|
||||
$this->passwordBroker->shouldReceive('createToken')->with($user)->once()->andReturn('random-token');
|
||||
|
||||
$this->repository->shouldReceive('create')->with([
|
||||
'password' => 'created-enc-password',
|
||||
|
@ -152,6 +152,6 @@ class UserCreationServiceTest extends TestCase
|
|||
*/
|
||||
private function getService(): UserCreationService
|
||||
{
|
||||
return new UserCreationService($this->connection, $this->hasher, $this->passwordService, $this->repository);
|
||||
return new UserCreationService($this->connection, $this->hasher, $this->passwordBroker, $this->repository);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue