1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-26 11:02:31 +01:00

Merge pull request #248 from Pterodactyl/feature/service-changes

Feature/service changes
This commit is contained in:
Dane Everitt 2017-01-12 15:48:42 -05:00 committed by GitHub
commit 31864de3f4
51 changed files with 2520 additions and 98 deletions

View File

@ -1,11 +1,12 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -3,6 +3,21 @@ 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.6.0-pre.1
### Added
* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon.
* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update.
* Users can now have a username as well as client name assigned to thier account.
### Fixed
* Bug causing error logs to be spammed if someone timed out on an ajax based page.
### Changed
* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields.
* User model now defines mass assignment fields using `$fillable` rather than `$guarded`.
### Deprecated
## v0.5.6 (Bodacious Boreopterus)
### Added
* Added the following languages: Estonian `et`, Dutch `nl`, Norwegian `nb` (partial), Romanian `ro`, and Russian `ru`. Interested in helping us translate the panel into more languages, or improving existing translations? Contact us on Discord and let us know.

View File

@ -0,0 +1,74 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Console\Commands;
use Carbon;
use Storage;
use Illuminate\Console\Command;
class CleanServiceBackup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'pterodactyl:cleanservices';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Cleans .bak files assocaited with service backups whene editing files through the panel.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$files = Storage::files('services/.bak');
foreach ($files as $file) {
$lastModified = Carbon::createFromTimestamp(Storage::lastModified($file));
if ($lastModified->diffInMinutes(Carbon::now()) > 5) {
$this->info('Deleting ' . $file);
Storage::delete($file);
}
}
}
}

View File

@ -21,6 +21,7 @@ class Kernel extends ConsoleKernel
\Pterodactyl\Console\Commands\ClearTasks::class,
\Pterodactyl\Console\Commands\ClearServices::class,
\Pterodactyl\Console\Commands\UpdateEmailSettings::class,
\Pterodactyl\Console\Commands\CleanServiceBackup::class,
];
/**
@ -33,5 +34,6 @@ class Kernel extends ConsoleKernel
{
$schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping();
$schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15);
$schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13);
}
}

View File

@ -46,13 +46,12 @@ class Handler extends ExceptionHandler
*/
public function render($request, Exception $exception)
{
if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) {
if ($request->expectsJson()) {
$response = response()->json([
'error' => ($exception instanceof DisplayException) ? $exception->getMessage() : 'An unhandled error occured while attempting to process this request.',
], 500);
], ($this->isHttpException($exception)) ? $e->getStatusCode() : 500);
// parent::render() will log it, we are bypassing it in this case.
Log::error($exception);
parent::report($exception);
}
return (isset($response)) ? $response : parent::render($request, $exception);

View File

@ -122,6 +122,9 @@ class UserController extends BaseController
{
try {
$user = new UserRepository;
$create = $user->create($request->only([
'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id',
]));
$create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id'));
return ['id' => $create];
@ -156,7 +159,9 @@ class UserController extends BaseController
{
try {
$user = new UserRepository;
$user->update($id, $request->all());
$user->update($id, $request->only([
'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language',
]));
return Models\User::findOrFail($id);
} catch (DisplayValidationException $ex) {

View File

@ -0,0 +1,252 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log;
use Alert;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\ServiceRepository\Pack;
use Pterodactyl\Exceptions\DisplayValidationException;
class PackController extends Controller
{
public function __construct()
{
//
}
protected function formatServices()
{
$options = Models\ServiceOptions::select(
'services.name AS p_service',
'service_options.id',
'service_options.name'
)->join('services', 'services.id', '=', 'service_options.parent_service')->get();
$array = [];
foreach ($options as &$option) {
if (! array_key_exists($option->p_service, $array)) {
$array[$option->p_service] = [];
}
$array[$option->p_service] = array_merge($array[$option->p_service], [[
'id' => $option->id,
'name' => $option->name,
]]);
}
return $array;
}
public function listAll(Request $request)
{
return view('admin.services.packs.index', [
'services' => Models\Service::all(),
]);
}
public function listByOption(Request $request, $id)
{
$option = Models\ServiceOptions::findOrFail($id);
return view('admin.services.packs.byoption', [
'packs' => Models\ServicePack::where('option', $option->id)->get(),
'service' => Models\Service::findOrFail($option->parent_service),
'option' => $option,
]);
}
public function listByService(Request $request, $id)
{
return view('admin.services.packs.byservice', [
'service' => Models\Service::findOrFail($id),
'options' => Models\ServiceOptions::select(
'service_options.id',
'service_options.name',
DB::raw('(SELECT COUNT(id) FROM service_packs WHERE service_packs.option = service_options.id) AS p_count')
)->where('parent_service', $id)->get(),
]);
}
public function new(Request $request, $opt = null)
{
return view('admin.services.packs.new', [
'services' => $this->formatServices(),
'packFor' => $opt,
]);
}
public function create(Request $request)
{
try {
$repo = new Pack;
$id = $repo->create($request->except([
'_token',
]));
Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.packs.edit', $id)->withInput();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new service pack.')->flash();
}
return redirect()->route('admin.services.packs.new', $request->input('option'))->withInput();
}
public function edit(Request $request, $id)
{
$pack = Models\ServicePack::findOrFail($id);
$option = Models\ServiceOptions::select('id', 'parent_service', 'name')->where('id', $pack->option)->first();
return view('admin.services.packs.edit', [
'pack' => $pack,
'services' => $this->formatServices(),
'files' => Storage::files('packs/' . $pack->uuid),
'service' => Models\Service::findOrFail($option->parent_service),
'option' => $option,
]);
}
public function update(Request $request, $id)
{
if (! is_null($request->input('action_delete'))) {
try {
$repo = new Pack;
$repo->delete($id);
Alert::success('The requested service pack has been deleted from the system.')->flash();
return redirect()->route('admin.services.packs');
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to delete this pack.')->flash();
}
return redirect()->route('admin.services.packs.edit', $id);
} else {
try {
$repo = new Pack;
$repo->update($id, $request->except([
'_token',
]));
Alert::success('Service pack has been successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.packs.edit', $id)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add edit this pack.')->flash();
}
return redirect()->route('admin.services.packs.edit', $id);
}
}
public function export(Request $request, $id, $files = false)
{
$pack = Models\ServicePack::findOrFail($id);
$json = [
'name' => $pack->name,
'version' => $pack->version,
'description' => $pack->dscription,
'selectable' => (bool) $pack->selectable,
'visible' => (bool) $pack->visible,
'build' => [
'memory' => $pack->build_memory,
'swap' => $pack->build_swap,
'cpu' => $pack->build_cpu,
'io' => $pack->build_io,
'container' => $pack->build_container,
'script' => $pack->build_script,
],
];
$filename = tempnam(sys_get_temp_dir(), 'pterodactyl_');
if ((bool) $files) {
$zip = new \ZipArchive;
if (! $zip->open($filename, \ZipArchive::CREATE)) {
abort(503, 'Unable to open file for writing.');
}
$files = Storage::files('packs/' . $pack->uuid);
foreach ($files as $file) {
$zip->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file)));
}
$zip->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT));
$zip->close();
return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true);
} else {
$fp = fopen($filename, 'a+');
fwrite($fp, json_encode($json, JSON_PRETTY_PRINT));
fclose($fp);
return response()->download($filename, 'pack-' . $pack->name . '.json', [
'Content-Type' => 'application/json',
])->deleteFileAfterSend(true);
}
}
public function uploadForm(Request $request, $for = null)
{
return view('admin.services.packs.upload', [
'services' => $this->formatServices(),
'for' => $for,
]);
}
public function postUpload(Request $request)
{
try {
$repo = new Pack;
$id = $repo->createWithTemplate($request->except([
'_token',
]));
Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.packs.edit', $id)->withInput();
} catch (DisplayValidationException $ex) {
return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new service pack.')->flash();
}
return redirect()->back();
}
}

View File

@ -253,7 +253,7 @@ class ServersController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function postNewServerServiceVariables(Request $request)
public function postNewServerOptionDetails(Request $request)
{
if (! $request->input('option')) {
return response()->json([
@ -269,6 +269,7 @@ class ServersController extends Controller
->first();
return response()->json([
'packs' => Models\ServicePack::select('id', 'name', 'version')->where('option', $request->input('option'))->where('selectable', true)->get(),
'variables' => Models\ServiceVariables::where('option_id', $request->input('option'))->get(),
'exec' => $option->executable,
'startup' => $option->startup,

View File

@ -27,6 +27,7 @@ namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log;
use Alert;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException;
@ -286,4 +287,39 @@ class ServiceController extends Controller
return redirect()->route('admin.services.option', [$service, $option]);
}
public function getConfiguration(Request $request, $serviceId)
{
$service = Models\Service::findOrFail($serviceId);
return view('admin.services.config', [
'service' => $service,
'contents' => [
'json' => Storage::get('services/' . $service->file . '/main.json'),
'index' => Storage::get('services/' . $service->file . '/index.js'),
],
]);
}
public function postConfiguration(Request $request, $serviceId)
{
try {
$repo = new ServiceRepository\Service;
$repo->updateFile($serviceId, $request->except([
'_token',
]));
return response('', 204);
} catch (DisplayException $ex) {
return response()->json([
'error' => $ex->getMessage(),
], 503);
} catch (\Exception $ex) {
Log::error($ex);
return response()->json([
'error' => 'An error occured while attempting to save the file.',
], 503);
}
}
}

View File

@ -116,7 +116,13 @@ class UserController extends Controller
{
try {
$user = new UserRepository;
$userid = $user->create($request->input('email'), $request->input('password'));
$userid = $user->create($request->only([
'email',
'password',
'name_first',
'name_last',
'username',
]));
Alert::success('Account has been successfully created.')->flash();
return redirect()->route('admin.users.view', $userid);
@ -132,19 +138,16 @@ class UserController extends Controller
public function updateUser(Request $request, $user)
{
$data = [
'email' => $request->input('email'),
'root_admin' => $request->input('root_admin'),
'password_confirmation' => $request->input('password_confirmation'),
];
if ($request->input('password')) {
$data['password'] = $request->input('password');
}
try {
$repo = new UserRepository;
$repo->update($user, $data);
$repo->update($user, $request->only([
'email',
'password',
'name_first',
'name_last',
'username',
'root_admin',
]));
Alert::success('User account was successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage()));

View File

@ -0,0 +1,79 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Controllers\Daemon;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\Controller;
class ServiceController extends Controller
{
/**
* Controller Constructor.
*/
public function __construct()
{
//
}
/**
* Returns a listing of all services currently on the system,
* as well as the associated files and the file hashes for
* caching purposes.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function list(Request $request)
{
$response = [];
foreach (Models\Service::all() as &$service) {
$response[$service->file] = [
'main.json' => sha1_file(storage_path('app/services/' . $service->file . '/main.json')),
'index.js' => sha1_file(storage_path('app/services/' . $service->file . '/index.js')),
];
}
return response()->json($response);
}
/**
* Returns the contents of the requested file for the given service.
*
* @param \Illuminate\Http\Request $request
* @param string $service
* @param string $file
* @return \Illuminate\Http\Response
*/
public function pull(Request $request, $service, $file)
{
if (! Storage::exists('services/' . $service . '/' . $file)) {
return response()->json(['error' => 'No such file.'], 404);
}
return response()->file(storage_path('app/services/' . $service . '/' . $file));
}
}

View File

@ -147,8 +147,8 @@ class AdminRoutes
'uses' => 'Admin\ServersController@postNewServerServiceOptions',
]);
$router->post('/new/service-variables', [
'uses' => 'Admin\ServersController@postNewServerServiceVariables',
$router->post('/new/option-details', [
'uses' => 'Admin\ServersController@postNewServerOptionDetails',
]);
// End Assorted Page Helpers
@ -383,6 +383,15 @@ class AdminRoutes
'uses' => 'Admin\ServiceController@deleteService',
]);
$router->get('/service/{id}/configuration', [
'as' => 'admin.services.service.config',
'uses' => 'Admin\ServiceController@getConfiguration',
]);
$router->post('/service/{id}/configuration', [
'uses' => 'Admin\ServiceController@postConfiguration',
]);
$router->get('/service/{service}/option/new', [
'as' => 'admin.services.option.new',
'uses' => 'Admin\ServiceController@newOption',
@ -424,5 +433,53 @@ class AdminRoutes
'uses' => 'Admin\ServiceController@deleteVariable',
]);
});
// Service Packs
$router->group([
'prefix' => 'admin/services/packs',
'middleware' => [
'auth',
'admin',
'csrf',
],
], function () use ($router) {
$router->get('/new/{option?}', [
'as' => 'admin.services.packs.new',
'uses' => 'Admin\PackController@new',
]);
$router->post('/new', [
'uses' => 'Admin\PackController@create',
]);
$router->get('/upload/{option?}', [
'as' => 'admin.services.packs.uploadForm',
'uses' => 'Admin\PackController@uploadForm',
]);
$router->post('/upload', [
'uses' => 'Admin\PackController@postUpload',
]);
$router->get('/', [
'as' => 'admin.services.packs',
'uses' => 'Admin\PackController@listAll',
]);
$router->get('/for/option/{option}', [
'as' => 'admin.services.packs.option',
'uses' => 'Admin\PackController@listByOption',
]);
$router->get('/for/service/{service}', [
'as' => 'admin.services.packs.service',
'uses' => 'Admin\PackController@listByService',
]);
$router->get('/edit/{pack}', [
'as' => 'admin.services.packs.edit',
'uses' => 'Admin\PackController@edit',
]);
$router->post('/edit/{pack}', [
'uses' => 'Admin\PackController@update',
]);
$router->get('/edit/{pack}/export/{archive?}', [
'as' => 'admin.services.packs.export',
'uses' => 'Admin\PackController@export',
]);
});
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class DaemonRoutes
{
public function map(Router $router)
{
$router->group(['prefix' => 'daemon'], function () use ($router) {
$router->get('services', [
'as' => 'daemon.services',
'uses' => 'Daemon\ServiceController@list',
]);
$router->get('services/pull/{service}/{file}', [
'as' => 'remote.install',
'uses' => 'Daemon\ServiceController@pull',
]);
});
}
}

53
app/Models/Checksum.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model;
class Checksum extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'checksums';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'service' => 'integer',
];
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model;
class ServicePack extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'service_packs';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'option' => 'integer',
'build_memory' => 'integer',
'build_swap' => 'integer',
'build_cpu' => 'integer',
'build_io' => 'integer',
'selectable' => 'boolean',
'visible' => 'boolean',
];
}

View File

@ -37,13 +37,24 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, Notifiable;
/**
* The rules for user passwords.
*
* @var string
*/
const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
/**
* The regex rules for usernames.
*
* @var string
*/
const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/';
/**
* The table associated with the model.
*
@ -52,11 +63,11 @@ class User extends Model implements
protected $table = 'users';
/**
* The attributes that are not mass assignable.
* A list of mass-assignable variables.
*
* @var array
* @var [type]
*/
protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at'];
protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar'];
/**
* Cast values to correct type.
@ -66,6 +77,7 @@ class User extends Model implements
protected $casts = [
'root_admin' => 'integer',
'use_totp' => 'integer',
'gravatar' => 'integer',
];
/**
@ -76,12 +88,10 @@ class User extends Model implements
protected $hidden = ['password', 'remember_token', 'totp_secret'];
/**
* The rules for user passwords.
* Determines if a user has permissions.
*
* @var string
* @return bool
*/
const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
public function permissions()
{
return $this->hasMany(Permission::class);

View File

@ -79,8 +79,9 @@ class ServerRepository
'io' => 'required|numeric|min:10|max:1000',
'cpu' => 'required|numeric|min:0',
'disk' => 'required|numeric|min:0',
'service' => 'bail|required|numeric|min:1|exists:services,id',
'option' => 'bail|required|numeric|min:1|exists:service_options,id',
'service' => 'required|numeric|min:1|exists:services,id',
'option' => 'required|numeric|min:1|exists:service_options,id',
'pack' => 'required|numeric|min:0',
'startup' => 'string',
'custom_image_name' => 'required_if:use_custom_image,on',
'auto_deploy' => 'sometimes|boolean',
@ -156,6 +157,18 @@ class ServerRepository
throw new DisplayException('The requested service option does not exist for the specified service.');
}
// Validate the Pack
if ($data['pack'] == 0) {
$data['pack'] = null;
}
if (! is_null($data['pack'])) {
$pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first();
if (! $pack) {
throw new DisplayException('The requested service pack does not seem to exist for this combination.');
}
}
// Load up the Service Information
$service = Models\Service::find($option->parent_service);
@ -247,6 +260,7 @@ class ServerRepository
'allocation' => $allocation->id,
'service' => $data['service'],
'option' => $data['option'],
'pack' => $data['pack'],
'startup' => $data['startup'],
'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
@ -311,6 +325,7 @@ class ServerRepository
'service' => [
'type' => $service->file,
'option' => $option->tag,
'pack' => (isset($pack)) ? $pack->uuid : null,
],
'keys' => [
(string) $server->daemonSecret => $this->daemonPermissions,

View File

@ -0,0 +1,237 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Repositories\ServiceRepository;
use DB;
use Uuid;
use Storage;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Pack
{
public function __construct()
{
//
}
public function create(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'sometimes|nullable|string',
'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean',
'build_memory' => 'required|integer|min:0',
'build_swap' => 'required|integer|min:0',
'build_cpu' => 'required|integer|min:0',
'build_io' => 'required|integer|min:10|max:1000',
'build_container' => 'required|string',
'build_script' => 'sometimes|nullable|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['file_upload'])) {
if (! $data['file_upload']->isValid()) {
throw new DisplayException('The file provided does not appear to be valid.');
}
if (! in_array($data['file_upload']->getMimeType(), [
'application/zip',
'application/gzip',
])) {
throw new DisplayException('The file provided does not meet the required filetypes of application/zip or application/gzip.');
}
}
DB::beginTransaction();
try {
$uuid = new UuidService;
$pack = Models\ServicePack::create([
'option' => $data['option'],
'uuid' => $uuid->generate('servers', 'uuid'),
'build_memory' => $data['build_memory'],
'build_swap' => $data['build_swap'],
'build_cpu' => $data['build_swap'],
'build_io' => $data['build_io'],
'build_script' => (empty($data['build_script'])) ? null : $data['build_script'],
'build_container' => $data['build_container'],
'name' => $data['name'],
'version' => $data['version'],
'description' => (empty($data['description'])) ? null : $data['description'],
'selectable' => isset($data['selectable']),
'visible' => isset($data['visible']),
]);
Storage::makeDirectory('packs/' . $pack->uuid);
if (isset($data['file_upload'])) {
$filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz';
$data['file_upload']->storeAs('packs/' . $pack->uuid, $filename);
}
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
return $pack->id;
}
public function createWithTemplate(array $data)
{
if (! isset($data['file_upload'])) {
throw new DisplayException('No template file was found submitted with this request.');
}
if (! $data['file_upload']->isValid()) {
throw new DisplayException('The file provided does not appear to be valid.');
}
if (! in_array($data['file_upload']->getMimeType(), [
'application/zip',
'text/plain',
'application/json',
])) {
throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetypes of application/zip or application/json.');
}
if ($data['file_upload']->getMimeType() === 'application/zip') {
$zip = new \ZipArchive;
if (! $zip->open($data['file_upload']->path())) {
throw new DisplayException('The uploaded archive was unable to be opened.');
}
$isZip = $zip->locateName('archive.zip');
$isTar = $zip->locateName('archive.tar.gz');
if ($zip->locateName('import.json') === false || ($isZip === false && $isTar === false)) {
throw new DisplayException('This contents of the provided archive were in an invalid format.');
}
$json = json_decode($zip->getFromName('import.json'));
$id = $this->create([
'name' => $json->name,
'version' => $json->version,
'description' => $json->description,
'option' => $data['option'],
'selectable' => $json->selectable,
'visible' => $json->visible,
'build_memory' => $json->build->memory,
'build_swap' => $json->build->swap,
'build_cpu' => $json->build->cpu,
'build_io' => $json->build->io,
'build_container' => $json->build->container,
'build_script' => $json->build->script,
]);
$pack = Models\ServicePack::findOrFail($id);
if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), ($isZip === false) ? 'archive.tar.gz' : 'archive.zip')) {
$pack->delete();
throw new DisplayException('Unable to extract the archive file to the correct location.');
}
$zip->close();
return $pack->id;
} else {
$json = json_decode(file_get_contents($data['file_upload']->path()));
return $this->create([
'name' => $json->name,
'version' => $json->version,
'description' => $json->description,
'option' => $data['option'],
'selectable' => $json->selectable,
'visible' => $json->visible,
'build_memory' => $json->build->memory,
'build_swap' => $json->build->swap,
'build_cpu' => $json->build->cpu,
'build_io' => $json->build->io,
'build_container' => $json->build->container,
'build_script' => $json->build->script,
]);
}
}
public function update($id, array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'string',
'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean',
'build_memory' => 'required|integer|min:0',
'build_swap' => 'required|integer|min:0',
'build_cpu' => 'required|integer|min:0',
'build_io' => 'required|integer|min:10|max:1000',
'build_container' => 'required|string',
'build_script' => 'sometimes|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
DB::transaction(function () use ($id, $data) {
Models\ServicePack::findOrFail($id)->update([
'option' => $data['option'],
'build_memory' => $data['build_memory'],
'build_swap' => $data['build_swap'],
'build_cpu' => $data['build_swap'],
'build_io' => $data['build_io'],
'build_script' => (empty($data['build_script'])) ? null : $data['build_script'],
'build_container' => $data['build_container'],
'name' => $data['name'],
'version' => $data['version'],
'description' => (empty($data['description'])) ? null : $data['description'],
'selectable' => isset($data['selectable']),
'visible' => isset($data['visible']),
]);
return true;
});
}
public function delete($id)
{
$pack = Models\ServicePack::findOrFail($id);
// @TODO Check for linked servers; foreign key should block this.
DB::transaction(function () use ($pack) {
$pack->delete();
Storage::deleteDirectory('packs/' . $pack->uuid);
});
}
}

View File

@ -26,6 +26,7 @@ namespace Pterodactyl\Repositories\ServiceRepository;
use DB;
use Uuid;
use Storage;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
@ -43,7 +44,7 @@ class Service
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|string',
'file' => 'required|regex:/^[\w.-]{1,50}$/',
'file' => 'required|unique:services,file|regex:/^[\w.-]{1,50}$/',
'executable' => 'max:255|regex:/^(.*)$/',
'startup' => 'string',
]);
@ -52,16 +53,24 @@ class Service
throw new DisplayValidationException($validator->errors());
}
if (Models\Service::where('file', $data['file'])->first()) {
throw new DisplayException('A service using that configuration file already exists on the system.');
}
$data['author'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4));
$service = new Models\Service;
DB::beginTransaction();
try {
$service->fill($data);
$service->save();
Storage::put('services/' . $data['file'] . '/main.json', '{}');
Storage::copy('services/.templates/index.js', 'services/' . $data['file'] . '/index.js');
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
return $service->id;
}
@ -101,10 +110,38 @@ class Service
Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete();
$options->delete();
$service->delete();
Storage::deleteDirectory('services/' . $service->file);
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function updateFile($id, array $data)
{
$service = Models\Service::findOrFail($id);
$validator = Validator::make($data, [
'file' => 'required|in:index,main',
'contents' => 'required|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$filename = ($data['file'] === 'main') ? 'main.json' : 'index.js';
$filepath = 'services/' . $service->file . '/' . $filename;
$backup = 'services/.bak/' . str_random(12) . '.bak';
try {
Storage::move($filepath, $backup);
Storage::put($filepath, $data['contents']);
} catch (\Exception $ex) {
Storage::move($backup, $filepath);
throw $ex;
}
}
}

View File

@ -29,6 +29,7 @@ use DB;
use Auth;
use Hash;
use Carbon;
use Settings;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
@ -52,18 +53,16 @@ class UserRepository
* @param int $token A custom user ID.
* @return bool|int
*/
public function create($email, $password = null, $admin = false, $token = null)
public function create(array $data)
{
$validator = Validator::make([
'email' => $email,
'password' => $password,
'root_admin' => $admin,
'custom_id' => $token,
], [
$validator = Validator::make($data, [
'email' => 'required|email|unique:users,email',
'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES,
'name_first' => 'required|string|between:1,255',
'name_last' => 'required|string|between:1,255',
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
'root_admin' => 'required|boolean',
'custom_id' => 'nullable|unique:users,id',
'custom_id' => 'sometimes|nullable|unique:users,id',
]);
// Run validator, throw catchable and displayable exception if it fails.
@ -79,18 +78,27 @@ class UserRepository
$uuid = new UuidService;
// Support for API Services
if (! is_null($token)) {
if (isset($data['custom_id']) && ! is_null($data['custom_id'])) {
$user->id = $token;
}
// UUIDs are not mass-fillable.
$user->uuid = $uuid->generate('users', 'uuid');
$user->email = $email;
$user->password = Hash::make((is_null($password)) ? str_random(30) : $password);
$user->language = 'en';
$user->root_admin = ($admin) ? 1 : 0;
$user->fill([
'email' => $data['email'],
'username' => $data['username'],
'name_first' => $data['name_first'],
'name_last' => $data['name_last'],
'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password),
'root_admin' => $data['root_admin'],
'language' => Settings::get('default_language', 'en'),
]);
$user->save();
// Setup a Password Reset to use when they set a password.
// Only used if no password is provided.
if (empty($data['password'])) {
$token = str_random(32);
DB::table('password_resets')->insert([
'email' => $user->email,
@ -99,6 +107,7 @@ class UserRepository
]);
$user->notify((new AccountCreated($token)));
}
DB::commit();
@ -122,7 +131,10 @@ class UserRepository
$validator = Validator::make($data, [
'email' => 'sometimes|required|email|unique:users,email,' . $id,
'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES,
'name_first' => 'sometimes|required|string|between:1,255',
'name_last' => 'sometimes|required|string|between:1,255',
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
'root_admin' => 'sometimes|required|boolean',
'language' => 'sometimes|required|string|min:1|max:5',
'use_totp' => 'sometimes|required|boolean',
@ -135,12 +147,15 @@ class UserRepository
throw new DisplayValidationException($validator->errors());
}
if (array_key_exists('password', $data)) {
// The password and root_admin fields are not mass assignable.
if (! empty($data['password'])) {
$data['password'] = Hash::make($data['password']);
} else {
unset($data['password']);
}
if (isset($data['password_confirmation'])) {
unset($data['password_confirmation']);
if (! empty($data['root_admin'])) {
$user->root_admin = $data['root_admin'];
}
$user->fill($data);

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPackSupport extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('service_packs', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('option');
$table->char('uuid', 36)->unique();
$table->unsignedInteger('build_memory')->nullable();
$table->unsignedInteger('build_swap')->nullable();
$table->unsignedInteger('build_cpu')->nullable();
$table->unsignedInteger('build_io')->nullable();
$table->text('build_script')->nullable();
$table->string('build_container')->default('alpine:latest');
$table->string('name');
$table->string('version');
$table->text('description')->nullable();
$table->boolean('selectable')->default(true);
$table->boolean('visible')->default(true);
$table->timestamps();
$table->foreign('option')->references('id')->on('service_options');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('service_packs');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SetServiceNameUnique extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('services', function (Blueprint $table) {
$table->unique('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('services', function (Blueprint $table) {
$table->dropUnique('services_name_unique');
});
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPackColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('servers', function (Blueprint $table) {
$table->unsignedInteger('pack')->nullable()->after('option');
$table->foreign('pack')->references('id')->on('service_packs');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('servers', function (Blueprint $table) {
$table->dropForeign('servers_pack_foreign');
$table->dropIndex('servers_pack_foreign');
$table->dropColumn('pack');
});
}
}

View File

@ -0,0 +1,50 @@
<?php
use Pterodactyl\Models\User;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddMoreUserData extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('name_first')->after('email')->nullable();
$table->string('name_last')->after('name_first')->nullable();
$table->string('username')->after('uuid');
$table->boolean('gravatar')->after('totp_secret')->default(true);
});
DB::transaction(function () {
foreach (User::all() as &$user) {
$user->username = $user->email;
$user->save();
}
});
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('name_first');
$table->dropColumn('name_last');
$table->dropColumn('username');
$table->dropColumn('gravatar');
});
}
}

View File

@ -325,3 +325,14 @@ td.has-progress {
padding:0;
border:0;
}
.fuelux .checkbox-formheight.checkbox-custom.checkbox-inline.highlight {
height: 36px;
padding: 10px 8px 4px 28px;
width: 100%;
}
.fuelux .checkbox-formheight.checkbox-custom.checkbox-inline.highlight:before {
left: 8px;
top: 11px;
}

View File

@ -201,6 +201,15 @@
<p class="text-muted"><small>Select the type of service that this server will be running.</small></p>
</div>
</div>
<div class="form-group col-md-12 hidden">
<label for="option" class="control-label">Service Pack</label>
<div>
<select name="pack" id="getPack" class="form-control">
<option disabled selected> -- Select a Service Pack</option>
</select>
<p class="text-muted"><small>Select the service pack that should be used for this server. This option can be changed later.</small></p>
</div>
</div>
</div>
</div>
</div>
@ -392,6 +401,7 @@ $(document).ready(function () {
handleLoader('#load_services', true);
$('#serviceOptions').slideUp();
$('#getOption').html('<option disabled selected> -- Select a Service Option</option>');
$('#getPack').html('<option disabled selected> -- Select a Service Pack</option>');
$.ajax({
method: 'POST',
@ -423,10 +433,11 @@ $(document).ready(function () {
handleLoader('#serviceOptions', true);
$('#serverVariables').html('');
$('input[name="custom_image_name"]').val($(this).find(':selected').data('image'));
$('#getPack').html('<option disabled selected> -- Select a Service Pack</option>');
$.ajax({
method: 'POST',
url: '/admin/servers/new/service-variables',
url: '/admin/servers/new/option-details',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
@ -436,6 +447,12 @@ $(document).ready(function () {
}).done(function (data) {
$('#startupExec').html(data.exec);
$('input[name="startup"]').val(data.startup);
$.each(data.packs, function (i, item) {
$('#getPack').append('<option value="' + item.id + '">' + item.name + ' (' + item.version + ')</option>');
});
$('#getPack').append('<option value="0">No Service Pack</option>').parent().parent().removeClass('hidden');
$.each(data.variables, function (i, item) {
var isRequired = (item.required === 1) ? '<span class="label label-primary">Required</span> ' : '';
var dataAppend = ' \

View File

@ -0,0 +1,180 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Manage Service Configuration
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">Configuration</li>
</ul>
<h3 class="nopad">Service Configuration</h3><hr />
<ul class="nav nav-tabs tabs_with_panel" id="config_tabs">
<li class="active"><a href="#tab_main" data-toggle="tab">main.json</a></li>
<li><a href="#tab_index" data-toggle="tab">index.js</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_main">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body" style="padding-top:0;">
<div class="row" style="border-bottom:1px solid #ccc;">
<div class="col-md-12" style="margin:0; padding:0;">
<div id="editor_json" style="height:500px;">{{ $contents['json'] }}</div>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<button type="submit" id="save_json" class="btn btn-sm btn-success">Save Configuration</button>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab_index">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body" style="padding-top:0;">
<div class="row" style="border-bottom:1px solid #ccc;">
<div class="col-md-12" style="margin:0; padding:0;">
<div id="editor_index" style="height:500px;">{{ $contents['index'] }}</div>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<button type="submit" id="save_index" class="btn btn-sm btn-success">Save Scripting</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
const JsonEditor = ace.edit('editor_json');
const IndexEditor = ace.edit('editor_index');
const Modelist = ace.require('ace/ext/modelist')
JsonEditor.setTheme('ace/theme/chrome');
JsonEditor.getSession().setMode('ace/mode/json');
JsonEditor.getSession().setUseWrapMode(true);
JsonEditor.setShowPrintMargin(false);
IndexEditor.setTheme('ace/theme/chrome');
IndexEditor.getSession().setMode('ace/mode/javascript');
IndexEditor.getSession().setUseWrapMode(true);
IndexEditor.setShowPrintMargin(false);
JsonEditor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
saveConfig();
},
readOnly: false
});
IndexEditor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
saveIndex();
},
readOnly: false
});
$('#save_json').on('click', function (e) {
e.preventDefault();
saveConfig();
});
$('#save_index').on('click', function (e) {
e.preventDefault();
saveIndex();
});
function saveConfig() {
$('#save_json').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({
type: 'POST',
url: '{{ route('admin.services.service.config', $service->id) }}',
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: {
file: 'main',
contents: JsonEditor.getValue()
}
}).done(function (data) {
$.notify({
message: 'Service configuration file has been saved successfully.'
}, {
type: 'success'
});
}).fail(function (jqXHR) {
$.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () {
$('#save_json').html('Save Configuration').removeClass('disabled');
});
}
function saveIndex() {
$('#save_json').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({
type: 'POST',
url: '{{ route('admin.services.service.config', $service->id) }}',
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: {
file: 'index',
contents: IndexEditor.getValue()
}
}).done(function (data) {
$.notify({
message: 'Service scripting file has been saved successfully.'
}, {
type: 'success'
});
}).fail(function (jqXHR) {
$.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () {
$('#save_json').html('Save Scripting').removeClass('disabled');
});
}
});
</script>
@endsection

View File

@ -33,9 +33,10 @@
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Service Type</th>
<th class="col-md-3">Service Type</th>
<th>Description</th>
<th class="text-center">Servers</th>
<th></th>
</tr>
</thead>
<tbody>
@ -44,9 +45,11 @@
<td><a href="{{ route('admin.services.service', $service->id) }}">{{ $service->name }}</a></td>
<td>{!! $service->description !!}</td>
<td class="text-center">{{ $service->c_servers }}</td>
<td class="text-center align-middle"><a href="{{ route('admin.services.service.config', $service->id) }}"><button class="btn btn-xxs btn-primary"><i class="fa fa-wrench"></i> Configure</button></a></td>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-center"><a href="{{ route('admin.services.new') }}"><i class="fa fa-plus"></i></a></td>

View File

@ -55,7 +55,7 @@
<input type="text" name="file" class="form-control" value="{{ old('file') }}" />
<span class="input-group-addon">/index.js</span>
</div>
<p class="text-muted"><small>This should be the name of the folder on the daemon that contains all of the service logic.</small></p>
<p class="text-muted"><small>This should be a unique alpha-numeric <code>(a-z)</code> name used to identify the service.</small></p>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Display Executable:</label>

View File

@ -0,0 +1,90 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Service Packs for {{ $option->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li><a href="{{ route('admin.services.packs.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">{{ $option->name }}</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Pack Name</th>
<th>Version</th>
<th>UUID</th>
<th>Selectable</th>
<th>Visible</th>
</tr>
</thead>
<tbody>
@foreach ($packs as $pack)
<tr>
<td><a href="{{ route('admin.services.packs.edit', $pack->id) }}">{{ $pack->name }}</a></td>
<td><code>{{ $pack->version }}</code></td>
<td><code>{{ $pack->uuid }}</code></td>
<td>@if($pack->selectable)<span class="label label-success"><i class="fa fa-check"></i></span>@else<span class="label label-default"><i class="fa fa-times"></i></span>@endif</td>
<td>@if($pack->visible)<span class="label label-success"><i class="fa fa-check"></i></span>@else<span class="label label-default"><i class="fa fa-times"></i></span>@endif</td>
</tr>
@endforeach
<tr>
<td colspan="5">
<a href="{{ route('admin.services.packs.new', $option->id) }}">
<button class="pull-right btn btn-xxs btn-primary"><i class="fa fa-plus"></i></button>
</a>
<a href="#upload" id="toggleUpload">
<button class="pull-right btn btn-xxs btn-default" style="margin-right:5px;"><i class="fa fa-upload"></i> Install from Template</button>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
$('#toggleUpload').on('click', function (event) {
event.preventDefault();
var element = $(this);
element.find('button').addClass('disabled');
$.ajax({
method: 'GET',
url: '{{ route('admin.services.packs.uploadForm', $option->id) }}'
}).fail(function (jqXhr) {
console.error(jqXhr);
alert('There was an error trying to create the upload form.');
}).success(function (data) {
$(data).modal();
}).always(function () {
element.find('button').removeClass('disabled');
});
});
});
</script>
@endsection

View File

@ -0,0 +1,67 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Service Packs for {{ $service->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li class="active">{{ $service->name }}</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Service Option</th>
<th>Total Packs</th>
</tr>
</thead>
<tbody>
@foreach ($options as $option)
<tr>
<td><a href="{{ route('admin.services.packs.option', $option->id) }}">{{ $option->name }}</a></td>
<td>{{ $option->p_count }}</td>
</tr>
@endforeach
<tr>
<td colspan="2">
<a href="{{ route('admin.services.packs.new') }}">
<button class="pull-right btn btn-xxs btn-primary"><i class="fa fa-plus"></i></button>
</a>
<a href="{{ route('admin.services.packs.new') }}">
<button class="pull-right btn btn-xxs btn-default" style="margin-right:5px;"><i class="fa fa-upload"></i> Install from Template</button>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
});
</script>
@endsection

View File

@ -0,0 +1,218 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Add New Service Pack
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li><a href="{{ route('admin.services.packs.service', $service->id) }}">{{ $service->name }}</a></li>
<li><a href="{{ route('admin.services.packs.option', $option->id) }}">{{ $option->name }}</a></li>
<li class="active">{{ $pack->name }} ({{ $pack->version }})</li>
</ul>
<h3 class="nopad">Manage Service Pack</h3><hr />
<form action="{{ route('admin.services.packs.edit', $pack->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Pack Name:</label>
<div>
<input type="text" name="name" value="{{ old('name', $pack->name) }}" placeholder="My Awesome Pack" class="form-control" />
<p class="text-muted"><small>The name of the pack which will be seen in dropdown menus and to users.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Pack Version:</label>
<div>
<input type="text" name="version" value="{{ old('version', $pack->version) }}" placeholder="v0.8.1" class="form-control" />
<p class="text-muted"><small>The version of the program included in this pack.</small></p>
</div>
</div>
<div class="col-md-12 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description', $pack->description) }}</textarea>
<p class="text-muted"><small>Provide a description of the pack which will be shown to users.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
@foreach($services as $service => $options)
<option disabled>{{ $service }}</option>
@foreach($options as $option)
<option value="{{ $option['id'] }}" @if($pack->id === (int) $option['id'])selected="selected"@endif>&nbsp;&nbsp; -- {{ $option['name'] }}</option>
@endforeach
@endforeach
</select>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="selectable" value="1" @if($pack->selectable)checked="checked"@endif> User Selectable
</label>
</div>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="visible" value="1" @if($pack->visible)checked="checked"@endif> Visible
</label>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">Build Parameters</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">Memory:</label>
<div class="input-group">
<input type="text" name="build_memory" class="form-control" value="{{ old('build_memory', $pack->build_memory) }}"/>
<span class="input-group-addon">MB</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">Swap:</label>
<div class="input-group">
<input type="text" name="build_swap" class="form-control" value="{{ old('build_swap', $pack->build_swap) }}"/>
<span class="input-group-addon">MB</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">CPU:</label>
<div class="input-group">
<input type="text" name="build_cpu" class="form-control" value="{{ old('build_cpu', $pack->build_cpu) }}"/>
<span class="input-group-addon">%</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">IO:</label>
<div class="input-group">
<input type="text" name="build_io" class="form-control" value="{{ old('build_io', $pack->build_io) }}"/>
<span class="input-group-addon">I/O</span>
</div>
</div>
<div class="form-group col-md-12">
<div>
<p class="text-muted"><small>If you would like to set limits on the build container you may do so above. Setting <code>memory</code>, <code>swap</code>, or <code>cpu</code> to <code>0</code> will allow unlimited resource utilization. IO must be in a range between <code>10</code> to <code>1000</code> and is a relative weighting to other container IO usage.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Build Container:</label>
<div>
<input type="text" name="build_container" class="form-control" value="{{ old('build_container', $pack->build_container) }}"/>
<p class="text-muted"><small>Provide the docker container image that will be used to build this service pack. This container is <em>only</em> used if a build script is provided below.</small></p>
</div>
</div>
<div class="form-group col-md-12">
<label class="control-label">Build Script:</label>
<div id="build_script" style="height:200px">{{ $pack->build_script }}</div>
<textarea id="editor_contents" name="build_script" class="hidden"></textarea>
<p class="text-muted"><small>This script will be run inside the container if provided. You should use this script to download any additional dependencies or compile packages as necessary on the node. Your uploaded archive (if provided), will be available in <code>/input</code> as <code>archive.tar.gz</code> or as <code>archive.zip</code> depending on what format you uploaded as. Your completed pack should be saved as <code>package.tar.gz</code> in the <code>/output</code> directory (e.g. <code>/output/package.tar.gz</code>).</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">Package Archive</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-12">
@if(count($files) > 1)
<div class="alert alert-danger"><strong>Warning!</strong> Service packs should only contain a single pack archive in either <code>.zip</code> or <code>.tar.gz</code> format. We've detected more than one file for this pack.</div>
@endif
<table class="table table-striped">
<thead>
<tr>
<th>Filename</th>
<th>File Size</th>
<th>SHA1 Hash</th>
<th>Last Modified</th>
</tr>
</thead>
<tbody>
@foreach($files as &$file)
<tr>
<td>{{ basename($file) }}</td>
<td><code>{{ Storage::size($file) }}</code> Bytes</td>
<td><code>{{ sha1_file(storage_path('app/' . $file)) }}</code></td>
<td>{{ Carbon::createFromTimestamp(Storage::lastModified($file))->toDateTimeString() }}</td>
</tr>
@endforeach
</tbody>
</table>
<p class="text-muted"><small>If you wish to modify or upload a new file it should be uploaded to <code>{{ storage_path('app/packs/' . $pack->uuid) }}</code> as either <code>archive.zip</code> or <code>archive.tar.gz</code>.</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! csrf_field() !!}
<input type="submit" name="action_submit" class="btn btn-sm btn-primary" value="Edit Service Pack" />
<button type="submit" name="action_delete" class="pull-right btn btn-sm btn-danger"><i class="fa fa-times"></i> Delete</button>
<a href="{{ route('admin.services.packs.export', $pack->id) }}"><button type="button" class="pull-right btn btn-sm btn-default" style="margin-right:10px;"><i class="fa fa-file"></i> Export</button></a>
<a href="{{ route('admin.services.packs.export', [ $pack->id, 'true' ]) }}"><button type="button" class="pull-right btn btn-sm btn-default" style="margin-right:10px;"><i class="fa fa-download"></i> Export with Files</button></a>
</div>
</div>
</form>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script type="text/javascript">
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
const Editor = ace.edit('build_script');
Editor.setTheme('ace/theme/chrome');
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
Editor.getSession().setMode('ace/mode/sh');
Editor.setOptions({
minLines: 12,
maxLines: Infinity
});
Editor.on('change', event => {
$('#editor_contents').val(Editor.getValue());
});
});
</script>
@endsection

View File

@ -0,0 +1,47 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Service Packs
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li class="active">Packs</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<div class="row">
@foreach ($services as $service)
<div class="col-md-6">
<a href="{{ route('admin.services.packs.service', $service->id) }}"><button class="btn btn-lg btn-primary" style="width:100%;margin-bottom:25px;">{{ $service->name }}</button></a>
</div>
@endforeach
</div>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
});
</script>
@endsection

View File

@ -0,0 +1,193 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Add New Service Pack
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li class="active">New Service Pack</li>
</ul>
<h3 class="nopad">New Service Pack</h3><hr />
<form action="{{ route('admin.services.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Pack Name:</label>
<div>
<input type="text" name="name" value="{{ old('name') }}" placeholder="My Awesome Pack" class="form-control" />
<p class="text-muted"><small>The name of the pack which will be seen in dropdown menus and to users.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Pack Version:</label>
<div>
<input type="text" name="version" value="{{ old('version') }}" placeholder="v0.8.1" class="form-control" />
<p class="text-muted"><small>The version of the program included in this pack.</small></p>
</div>
</div>
<div class="col-md-12 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description') }}</textarea>
<p class="text-muted"><small>Provide a description of the pack which will be shown to users.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
@foreach($services as $service => $options)
<option disabled>{{ $service }}</option>
@foreach($options as $option)
<option value="{{ $option['id'] }}" @if((int) $packFor === (int) $option['id'])selected="selected"@endif>&nbsp;&nbsp; -- {{ $option['name'] }}</option>
@endforeach
@endforeach
</select>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="selectable" value="1"> User Selectable
</label>
</div>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="visible" value="1"> Visible
</label>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">Build Parameters</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">Memory:</label>
<div class="input-group">
<input type="text" name="build_memory" class="form-control" value="{{ old('build_memory', 0) }}"/>
<span class="input-group-addon">MB</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">Swap:</label>
<div class="input-group">
<input type="text" name="build_swap" class="form-control" value="{{ old('build_swap', 0) }}"/>
<span class="input-group-addon">MB</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">CPU:</label>
<div class="input-group">
<input type="text" name="build_cpu" class="form-control" value="{{ old('build_cpu', 0) }}"/>
<span class="input-group-addon">%</span>
</div>
</div>
<div class="form-group col-md-3 col-xs-6">
<label class="control-label">IO:</label>
<div class="input-group">
<input type="text" name="build_io" class="form-control" value="{{ old('build_io', 300) }}"/>
<span class="input-group-addon">I/O</span>
</div>
</div>
<div class="form-group col-md-12">
<div>
<p class="text-muted"><small>If you would like to set limits on the build container you may do so above. Setting <code>memory</code>, <code>swap</code>, or <code>cpu</code> to <code>0</code> will allow unlimited resource utilization. IO must be in a range between <code>10</code> to <code>1000</code> and is a relative weighting to other container IO usage.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Build Container:</label>
<div>
<input type="text" name="build_container" class="form-control" value="{{ old('build_container', 'alpine:latest') }}"/>
<p class="text-muted"><small>Provide the docker container image that will be used to build this service pack. This container is <em>only</em> used if a build script is provided below.</small></p>
</div>
</div>
<div class="form-group col-md-12">
<label class="control-label">Build Script:</label>
<div id="build_script" style="height:200px"></div>
<textarea id="editor_contents" name="build_script" class="hidden"></textarea>
<p class="text-muted"><small>This script will be run inside the container if provided. You should use this script to download any additional dependencies or compile packages as necessary on the node. Your uploaded archive (if provided), will be available in <code>/input</code> as <code>archive.tar.gz</code> or as <code>archive.zip</code> depending on what format you uploaded as. Your completed pack should be saved as <code>package.tar.gz</code> in the <code>/output</code> directory (e.g. <code>/output/package.tar.gz</code>).</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">File Upload</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Package Archive:</label>
<input name="file_upload" type="file" accept=".zip,.tar.gz, application/zip, application/gzip" />
<p class="text-muted"><small>This package file must either be a <code>.zip</code> or <code>.tar.gz</code> archive of files to use for either building or running this pack.<br /><br />If your file is larger than <code>20MB</code> we recommend uploading it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file.
This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add Service Pack" />
</div>
</div>
</form>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script type="text/javascript">
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
const Editor = ace.edit('build_script');
Editor.setTheme('ace/theme/chrome');
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
Editor.getSession().setMode('ace/mode/sh');
Editor.setOptions({
minLines: 12,
maxLines: Infinity
});
Editor.setValue('{{ old('build_script') }}');
Editor.on('change', event => {
$('#editor_contents').val(Editor.getValue());
});
});
</script>
@endsection

View File

@ -0,0 +1,45 @@
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="{{ route('admin.services.packs.uploadForm') }}" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Install Pack from Template</h4>
</div>
<div class="modal-body">
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="col-md-12">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
@foreach($services as $service => $options)
<option disabled>{{ $service }}</option>
@foreach($options as $option)
<option value="{{ $option['id'] }}" @if((int) $for === (int) $option['id'])selected="selected"@endif>&nbsp;&nbsp; -- {{ $option['name'] }}</option>
@endforeach
@endforeach
</select>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Package Archive:</label>
<input name="file_upload" type="file" accept=".zip,.json, application/json, application/zip" />
<p class="text-muted"><small>This file should be either the <code>.json</code> template file, or a <code>.zip</code> pack archive containing <code>archive.(zip|tar.gz)</code> and <code>import.json</code> within.<br /><br />This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
{!! csrf_field() !!}
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">Cancel</button>
<input type="submit" class="btn btn-primary btn-sm" value="Install" />
</div>
</form>
</div>
</div>
</div>

View File

@ -106,6 +106,7 @@
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Save Changes" />
<a href="{{ route('admin.services.service.config', $service->id) }}"><button type="button" class="pull-right btn btn-sm btn-default">Manage Configuration</button></a>
</div>
</div>
</form>

View File

@ -42,17 +42,21 @@
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Account Created</th>
<th>Account Updated</th>
<th>ID</td>
<th>Email</td>
<th>Client Name</th>
<th>Username</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td><a href="/admin/users/view/{{ $user->id }}"><code>{{ $user->email }}</code></a> @if($user->root_admin === 1)<span class="badge">Administrator</span>@endif</td>
<td>{{ $user->created_at }}</td>
<td>{{ $user->updated_at }}</td>
<tr class="align-middle">
<td><code>#{{ $user->id }}</code></td>
<td><a href="{{ route('admin.users.view', $user->id) }}">{{ $user->email }}</a></td>
<td>{{ $user->name_last }}, {{ $user->name_first }}</td>
<td><code>{{ $user->username }}</code></td>
<td class="text-center"><img src="https://www.gravatar.com/avatar/{{ md5(strtolower($user->email)) }}?s=20" class="img-circle" /></td>
</tr>
@endforeach
</tbody>

View File

@ -34,15 +34,38 @@
<h3>Create New Account</h3><hr />
<form action="new" method="post">
<fieldset>
<div class="form-group">
<div class="row">
<div class="form-group col-md-6">
<label for="email" class="control-label">Email</label>
<div>
<input type="text" autocomplete="off" name="email" class="form-control" />
<input type="text" autocomplete="off" name="email" value="{{ old('email') }}" class="form-control" />
</div>
</div>
<div class="form-group col-md-6">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" autocomplete="off" name="username" value="{{ old('username') }}" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label for="name_first" class="control-label">Client First Name</label>
<div>
<input type="text" autocomplete="off" name="name_first" value="{{ old('name_first') }}" class="form-control" />
</div>
</div>
<div class="form-group col-md-6">
<label for="name_last" class="control-label">Client Last Name</label>
<div>
<input type="text" autocomplete="off" name="name_last" value="{{ old('name_last') }}" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="well well-sm">
<hr />
<div class="alert alert-info">
<p>Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.</p>
</div>
</div>

View File

@ -31,7 +31,9 @@
<li><a href="/admin/users">Accounts</a></li>
<li class="active">{{ $user->email }}</li>
</ul>
<h3>Viewing User: {{ $user->email }}</h3><hr />
<h3 style="margin-bottom: 5px;">Viewing User: {{ $user->email }}</h3>
<p class="text-muted" style="margin: 0 0 -10.5px !important;"><small>Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}</small></p>
<hr />
<div class="row">
<form action="{{ route('admin.users.view', $user->id) }}" method="post">
<div class="col-md-6">
@ -43,19 +45,21 @@
</div>
</div>
<div class="form-group">
<label for="registered" class="control-label">{{ trans('strings.registered') }}</label>
<label for="registered" class="control-label">Username</label>
<div>
<input type="text" value="{{ $user->created_at }}" readonly="readonly" class="form-control">
<input type="text" name="username" value="{{ $user->username }}" class="form-control">
</div>
</div>
<div class="form-group">
<label for="root_admin" class="control-label">{{ trans('strings.root_administrator') }}</label>
<label for="registered" class="control-label">Client First Name</label>
<div>
<select name="root_admin" class="form-control">
<option value="0">{{ trans('strings.no') }}</option>
<option value="1" @if($user->root_admin)selected="selected"@endif>{{ trans('strings.yes') }}</option>
</select>
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
<input type="text" name="name_first" value="{{ $user->name_first }}" class="form-control">
</div>
</div>
<div class="form-group">
<label for="registered" class="control-label">Client Last Name</label>
<div>
<input type="text" name="name_last" value="{{ $user->name_last }}" class="form-control">
</div>
</div>
<div class="form-group">
@ -66,7 +70,6 @@
</div>
<div class="col-md-6">
<div class="well" style="padding-bottom: 0;">
<h4 class="nopad">{{ trans('base.account.update_pass') }}</h5><hr />
<div class="alert alert-success" style="display:none;margin-bottom:10px;" id="gen_pass"></div>
<div class="form-group">
<label for="password" class="control-label">{{ trans('strings.password') }}</label>
@ -74,16 +77,22 @@
<input type="password" id="password" name="password" class="form-control">
</div>
</div>
<div class="form-group">
<label for="password_confirmation" class="control-label">{{ trans('auth.confirmpassword') }}</label>
<div>
<input type="password" id="password_confirmation" name="password_confirmation" class="form-control">
</div>
</div>
<div class="form-group">
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
</div>
</div>
<div class="well" style="padding-bottom: 0;">
<div class="form-group">
<label for="root_admin" class="control-label">{{ trans('strings.root_administrator') }}</label>
<div>
<select name="root_admin" class="form-control">
<option value="0">{{ trans('strings.no') }}</option>
<option value="1" @if($user->root_admin)selected="selected"@endif>{{ trans('strings.yes') }}</option>
</select>
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@ -35,6 +35,23 @@
{!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!}
{!! Theme::js('js/vendor/fuelux/fuelux.min.js') !!}
{!! Theme::js('js/admin.min.js') !!}
{!! Theme::js('js/bootstrap-notify.min.js') !!}
<script>
$(document).ready(function () {
$.notifyDefaults({
placement: {
from: 'bottom',
align: 'right'
},
newest_on_top: true,
delay: 2000,
animate: {
enter: 'animated fadeInUp',
exit: 'animated fadeOutDown'
}
});
});
</script>
@show
<title>{{ Settings::get('company') }} - @yield('title')</title>
</head>
@ -85,6 +102,7 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Services <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/services/packs">Service Packs</a></li>
<li><a href="/admin/services">List Services</a></li>
<li><a href="/admin/services/new">Add Service</a></li>
</ul>
@ -144,6 +162,7 @@
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Service Management</strong></a>
<a href="/admin/services/packs" class="list-group-item">Service Packs</a>
<a href="/admin/services" class="list-group-item">List Services</a>
<a href="/admin/services/new" class="list-group-item">Add Service</a>
</div>

View File

@ -1,2 +1,6 @@
*
!.gitignore
!services/*
packs/**/*
services/.bak/*
!packs/.githold

View File

View File

@ -0,0 +1,31 @@
'use strict';
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -0,0 +1,38 @@
'use strict';
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
class Service extends Core {
onConsole(data) {
// Hide the output spam from Bungeecord getting pinged.
if (_.endsWith(data, '<-> InitialHandler has connected')) return;
return super.onConsole(data);
}
}
module.exports = Service;

View File

@ -0,0 +1,74 @@
{
"vanilla": {
"startup": {
"done": ")! For help, type ",
"userInteraction": [
"Go to eula.txt for more info."
]
},
"stop": "stop",
"configs": {
"server.properties": {
"parser": "properties",
"find": {
"server-ip": "0.0.0.0",
"enable-query": "true",
"server-port": "{{ server.build.default.port }}",
"query.port": "{{ server.build.default.port }}"
}
}
},
"log": {
"custom": false,
"location": "logs/latest.log"
},
"query": "minecraftping"
},
"spigot": {
"symlink": "vanilla",
"configs": {
"spigot.yml": {
"parser": "yaml",
"find": {
"settings.restart-on-crash": "false"
}
}
}
},
"bungeecord": {
"startup": {
"done": "Listening on ",
"userInteraction": [
"Listening on /0.0.0.0:25577"
]
},
"stop": "end",
"configs": {
"config.yml": {
"parser": "yaml",
"find": {
"listeners[0].query_enabled": true,
"listeners[0].query_port": "{{ server.build.default.port }}",
"listeners[0].host": "0.0.0.0:{{ server.build.default.port }}",
"servers.*.address": {
"127.0.0.1": "{{ config.docker.interface }}",
"localhost": "{{ config.docker.interface }}"
}
}
}
},
"log": {
"custom": false,
"location": "proxy.log.0"
},
"query": "minecraftping"
},
"sponge": {
"symlink": "vanilla",
"startup": {
"userInteraction": [
"You need to agree to the EULA"
]
}
}
}

View File

@ -0,0 +1,31 @@
'use strict';
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -0,0 +1,23 @@
{
"srcds": {
"startup": {
"done": "Assigned anonymous gameserver Steam ID",
"userInteraction": []
},
"stop": "quit",
"configs": {},
"log": {
"custom": true,
"location": "logs/latest.log"
},
"query": "protocol-valve"
},
"ark": {
"symlink": "srcds",
"startup": {
"done": "Setting breakpad minidump AppID"
},
"stop": "^C",
"query": "none"
}
}

View File

@ -0,0 +1,31 @@
'use strict';
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -0,0 +1,23 @@
{
"tshock": {
"startup": {
"done": "Type 'help' for a list of commands",
"userInteraction": []
},
"stop": "exit",
"configs": {
"tshock/config.json": {
"parser": "json",
"find": {
"ServerPort": "{{ server.build.default.port }}",
"MaxSlots": "{{ server.build.env.MAX_SLOTS }}"
}
}
},
"log": {
"custom": false,
"location": "ServerLog.txt"
},
"query": "none"
}
}

View File

@ -0,0 +1,31 @@
'use strict';
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -0,0 +1,50 @@
{
"mumble": {
"startup": {
"done": "Server listening on",
"userInteraction": [
"Generating new server certificate"
]
},
"stop": "^C",
"configs": {
"murmur.ini": {
"parser": "ini",
"find": {
"logfile": "murmur.log",
"port": "{{ server.build.default.port }}",
"host": "0.0.0.0",
"users": "{{ server.build.env.MAX_USERS }}"
}
}
},
"log": {
"custom": true,
"location": "logs/murmur.log"
},
"query": "mumbleping"
},
"ts3": {
"startup": {
"done": "listening on 0.0.0.0:",
"userInteraction": []
},
"stop": "^C",
"configs": {
"ts3server.ini": {
"parser": "ini",
"find": {
"default_voice_port": "{{ server.build.default.port }}",
"voice_ip": "0.0.0.0",
"query_port": "{{ server.build.default.port }}",
"query_ip": "0.0.0.0"
}
}
},
"log": {
"custom": true,
"location": "logs/ts3.log"
},
"query": "none"
}
}