Merge pull request #64 from Pterodactyl/v0.3.0-beta

merge v0.3.0 into development
This commit is contained in:
Dane Everitt 2016-02-27 10:35:34 -05:00
commit d96aff3086
42 changed files with 2294 additions and 687 deletions

View file

@ -2,6 +2,7 @@ APP_ENV=production
APP_DEBUG=false
APP_KEY=SomeRandomString3232RandomString
APP_THEME=default
APP_TIMEZONE=UTC
DB_HOST=localhost
DB_PORT=3306

View file

@ -30,6 +30,8 @@ SOFTWARE.
### Credits
Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/)
Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/)
BinaryJS - [license](https://github.com/binaryjs/binaryjs/blob/master/LICENSE) - [homepage](http://binaryjs.com)
Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com)

View file

@ -1,145 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Controllers\Admin;
use Alert;
use Settings;
use Mail;
use Log;
use Pterodactyl\Models\User;
use Pterodactyl\Repositories\UserRepository;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AccountsController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
//
}
public function getIndex(Request $request)
{
return view('admin.accounts.index', [
'users' => User::paginate(20)
]);
}
public function getNew(Request $request)
{
return view('admin.accounts.new');
}
public function getView(Request $request, $id)
{
return view('admin.accounts.view', [
'user' => User::findOrFail($id),
'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location')
->join('nodes', 'servers.node', '=', 'nodes.id')
->join('locations', 'nodes.location', '=', 'locations.id')
->where('owner', $id)
->where('active', 1)
->get(),
]);
}
public function deleteView(Request $request, $id)
{
try {
User::findOrFail($id)->delete();
return response(null, 204);
} catch(\Exception $ex) {
Log::error($ex);
return response()->json([
'error' => 'An error occured while attempting to delete this user.'
], 500);
}
}
public function postNew(Request $request)
{
try {
$user = new UserRepository;
$userid = $user->create($request->input('email'), $request->input('password'));
Alert::success('Account has been successfully created.')->flash();
return redirect()->route('admin.accounts.view', ['id' => $userid]);
} catch (\Pterodactyl\Exceptions\DisplayValidationException $ex) {
return redirect()->route('admin.accounts.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new user. ' . $ex->getMessage())->flash();
return redirect()->route('admin.accounts.new');
}
}
public function postUpdate(Request $request)
{
$this->validate($request, [
'email' => 'required|email|unique:users,email,'.$request->input('user'),
'root_admin' => 'required',
'password' => 'required_with:password_confirmation|confirmed',
'password_confirmation' => 'required_with:password'
]);
try {
$users = new UserRepository;
$user = [
'email' => $request->input('email'),
'root_admin' => $request->input('root_admin')
];
if(!empty($request->input('password'))) {
$user['password'] = $request->input('password');
}
if(!$users->update($request->input('user'), $user)) {
throw new \Exception('Unable to update user, response was not valid.');
}
if($request->input('email_user')) {
Mail::queue('emails.new_password', ['user' => User::findOrFail($request->input('user')), 'password' => $request->input('password')], function($message) use ($request) {
$message->to($request->input('email'))->subject(Settings::get('company') . ' - Admin Reset Password');
$message->from(Settings::get('email_from', env('MAIL_FROM')), Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel')));
});
}
Alert::success('User account was successfully updated.')->flash();
return redirect()->route('admin.accounts.view', ['id' => $request->input('user')]);
} catch (\Exception $e) {
Log::error($e);
Alert::danger('An error occured while attempting to update this user. ' . $e->getMessage())->flash();
return redirect()->route('admin.accounts.view', ['id' => $request->input('user')]);
}
}
}

View file

@ -0,0 +1,273 @@
<?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 Alert;
use DB;
use Log;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Repositories\ServiceRepository;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ServiceController extends Controller
{
public function __construct()
{
//
}
public function getIndex(Request $request)
{
return view('admin.services.index', [
'services' => Models\Service::select(
'services.*',
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.service = services.id) as c_servers')
)->get()
]);
}
public function getNew(Request $request)
{
return view('admin.services.new');
}
public function postNew(Request $request)
{
try {
$repo = new ServiceRepository\Service;
$id = $repo->create($request->except([
'_token'
]));
Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.service', $id);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.new')->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.')->flash();
}
return redirect()->route('admin.services.new')->withInput();
}
public function getService(Request $request, $service)
{
return view('admin.services.view', [
'service' => Models\Service::findOrFail($service),
'options' => Models\ServiceOptions::select(
'service_options.*',
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.option = service_options.id) as c_servers')
)->where('parent_service', $service)->get()
]);
}
public function postService(Request $request, $service)
{
try {
$repo = new ServiceRepository\Service;
$repo->update($service, $request->except([
'_token'
]));
Alert::success('Successfully updated this service.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.service', $service)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occurred while attempting to update this service.')->flash();
}
return redirect()->route('admin.services.service', $service)->withInput();
}
public function deleteService(Request $request, $service)
{
try {
$repo = new ServiceRepository\Service;
$repo->delete($service);
Alert::success('Successfully deleted that service.')->flash();
return redirect()->route('admin.services');
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error was encountered while attempting to delete that service.')->flash();
}
return redirect()->route('admin.services.service', $service);
}
public function getOption(Request $request, $service, $option)
{
$opt = Models\ServiceOptions::findOrFail($option);
return view('admin.services.options.view', [
'service' => Models\Service::findOrFail($opt->parent_service),
'option' => $opt,
'variables' => Models\ServiceVariables::where('option_id', $option)->get(),
'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail')
->join('users', 'users.id', '=', 'servers.owner')
->where('option', $option)
->paginate(10)
]);
}
public function postOption(Request $request, $service, $option)
{
try {
$repo = new ServiceRepository\Option;
$repo->update($option, $request->except([
'_token'
]));
Alert::success('Option settings successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option', [$service, $option])->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to modify this option.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option])->withInput();
}
public function deleteOption(Request $request, $service, $option)
{
try {
$service = Models\ServiceOptions::select('parent_service')->where('id', $option)->first();
$repo = new ServiceRepository\Option;
$repo->delete($option);
Alert::success('Successfully deleted that option.')->flash();
return redirect()->route('admin.services.service', $service->parent_service);
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error was encountered while attempting to delete this option.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option]);
}
public function postOptionVariable(Request $request, $service, $option, $variable)
{
try {
$repo = new ServiceRepository\Variable;
// Because of the way old() works on the display side we prefix all of the variables with thier ID
// We need to remove that prefix here since the repo doesn't want it.
$data = [];
foreach($request->except(['_token']) as $id => $val) {
$data[str_replace($variable.'_', '', $id)] = $val;
}
$repo->update($variable, $data);
Alert::success('Successfully updated variable.')->flash();
} catch (DisplayValidationException $ex) {
$data = [];
foreach(json_decode($ex->getMessage(), true) as $id => $val) {
$data[$variable.'_'.$id] = $val;
}
return redirect()->route('admin.services.option', [$service, $option])->withErrors((object) $data)->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occurred while attempting to update this service.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option])->withInput();
}
public function getNewVariable(Request $request, $service, $option)
{
return view('admin.services.options.variable', [
'service' => Models\Service::findOrFail($service),
'option' => Models\ServiceOptions::where('parent_service', $service)->where('id', $option)->firstOrFail()
]);
}
public function postNewVariable(Request $request, $service, $option)
{
try {
$repo = new ServiceRepository\Variable;
$repo->create($option, $request->except([
'_token'
]));
Alert::success('Successfully added new variable to this option.')->flash();
return redirect()->route('admin.services.option', [$service, $option])->withInput();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.variable.new', [$service, $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 occurred while attempting to add this variable.')->flash();
}
return redirect()->route('admin.services.option.variable.new', [$service, $option])->withInput();
}
public function newOption(Request $request, $service)
{
return view('admin.services.options.new', [
'service' => Models\Service::findOrFail($service),
]);
}
public function postNewOption(Request $request, $service)
{
try {
$repo = new ServiceRepository\Option;
$id = $repo->create($service, $request->except([
'_token'
]));
Alert::success('Successfully created new service option.')->flash();
return redirect()->route('admin.services.option', [$service, $id]);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.new', $service)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add this service option.')->flash();
}
return redirect()->route('admin.services.option.new', $service)->withInput();
}
public function deleteVariable(Request $request, $service, $option, $variable)
{
try {
$repo = new ServiceRepository\Variable;
$repo->delete($variable);
Alert::success('Deleted variable.')->flash();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to delete that variable.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option]);
}
}

View file

@ -0,0 +1,134 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Controllers\Admin;
use Alert;
use Settings;
use Mail;
use Log;
use Pterodactyl\Models\User;
use Pterodactyl\Repositories\UserRepository;
use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
//
}
public function getIndex(Request $request)
{
return view('admin.users.index', [
'users' => User::paginate(20)
]);
}
public function getNew(Request $request)
{
return view('admin.users.new');
}
public function getView(Request $request, $id)
{
return view('admin.users.view', [
'user' => User::findOrFail($id),
'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location')
->join('nodes', 'servers.node', '=', 'nodes.id')
->join('locations', 'nodes.location', '=', 'locations.id')
->where('owner', $id)
->where('active', 1)
->get(),
]);
}
public function deleteUser(Request $request, $id)
{
try {
$repo = new UserRepository;
$repo->delete($id);
Alert::success('Successfully deleted user from system.')->flash();
return redirect()->route('admin.users');
} catch(DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An exception was encountered while attempting to delete this user.')->flash();
}
return redirect()->route('admin.users.view', $id);
}
public function postNew(Request $request)
{
try {
$user = new UserRepository;
$userid = $user->create($request->input('email'), $request->input('password'));
Alert::success('Account has been successfully created.')->flash();
return redirect()->route('admin.users.view', $userid);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new user.')->flash();
return redirect()->route('admin.users.new');
}
}
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);
Alert::success('User account was successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage()));
} catch (\Exception $e) {
Log::error($e);
Alert::danger('An error occured while attempting to update this user.')->flash();
}
return redirect()->route('admin.users.view', $user);
}
}

View file

@ -29,7 +29,7 @@ use Hash;
use Google2FA;
use Alert;
use Pterodactyl\Models\Server;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
@ -55,7 +55,7 @@ class IndexController extends Controller
public function getIndex(Request $request)
{
return view('base.index', [
'servers' => Server::getUserServers(10),
'servers' => Models\Server::getUserServers(10),
]);
}
@ -72,14 +72,16 @@ class IndexController extends Controller
}
/**
* Returns TOTP Management Page.
* Returns Security Management Page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getAccountTotp(Request $request)
public function getAccountSecurity(Request $request)
{
return view('base.totp');
return view('base.security', [
'sessions' => Models\Session::where('user_id', Auth::user()->id)->get()
]);
}
/**
@ -227,4 +229,11 @@ class IndexController extends Controller
}
public function getRevokeSession(Request $request, $id)
{
$session = Models\Session::where('id', $id)->where('user_id', Auth::user()->id)->firstOrFail();
$session->delete();
return redirect()->route('account.security');
}
}

View file

@ -59,7 +59,7 @@ class AdminRoutes {
});
$router->group([
'prefix' => 'admin/accounts',
'prefix' => 'admin/users',
'middleware' => [
'auth',
'admin',
@ -69,35 +69,35 @@ class AdminRoutes {
// View All Accounts on System
$router->get('/', [
'as' => 'admin.accounts',
'uses' => 'Admin\AccountsController@getIndex'
'as' => 'admin.users',
'uses' => 'Admin\UserController@getIndex'
]);
// View Specific Account
$router->get('/view/{id}', [
'as' => 'admin.accounts.view',
'uses' => 'Admin\AccountsController@getView'
'as' => 'admin.users.view',
'uses' => 'Admin\UserController@getView'
]);
// Show Create Account Page
$router->get('/new', [
'as' => 'admin.accounts.new',
'uses' => 'Admin\AccountsController@getNew'
]);
// Handle Creating New Account
$router->post('/new', [
'uses' => 'Admin\AccountsController@postNew'
]);
// Update A Specific Account
$router->post('/update', [
'uses' => 'Admin\AccountsController@postUpdate'
// View Specific Account
$router->post('/view/{id}', [
'uses' => 'Admin\UserController@updateUser'
]);
// Delete an Account Matching an ID
$router->delete('/view/{id}', [
'uses' => 'Admin\AccountsController@deleteView'
'uses' => 'Admin\UserController@deleteUser'
]);
// Show Create Account Page
$router->get('/new', [
'as' => 'admin.users.new',
'uses' => 'Admin\UserController@getNew'
]);
// Handle Creating New Account
$router->post('/new', [
'uses' => 'Admin\UserController@postNew'
]);
});
@ -334,6 +334,84 @@ class AdminRoutes {
]);
});
// Service Routes
$router->group([
'prefix' => 'admin/services',
'middleware' => [
'auth',
'admin',
'csrf'
]
], function () use ($router) {
$router->get('/', [
'as' => 'admin.services',
'uses' => 'Admin\ServiceController@getIndex'
]);
$router->get('/new', [
'as' => 'admin.services.new',
'uses' => 'Admin\ServiceController@getNew'
]);
$router->post('/new', [
'uses' => 'Admin\ServiceController@postNew'
]);
$router->get('/service/{id}', [
'as' => 'admin.services.service',
'uses' => 'Admin\ServiceController@getService'
]);
$router->post('/service/{id}', [
'uses' => 'Admin\ServiceController@postService'
]);
$router->delete('/service/{id}', [
'uses' => 'Admin\ServiceController@deleteService'
]);
$router->get('/service/{service}/option/new', [
'as' => 'admin.services.option.new',
'uses' => 'Admin\ServiceController@newOption'
]);
$router->post('/service/{service}/option/new', [
'uses' => 'Admin\ServiceController@postNewOption'
]);
$router->get('/service/{service}/option/{option}', [
'as' => 'admin.services.option',
'uses' => 'Admin\ServiceController@getOption'
]);
$router->post('/service/{service}/option/{option}', [
'uses' => 'Admin\ServiceController@postOption'
]);
$router->delete('/service/{service}/option/{id}', [
'uses' => 'Admin\ServiceController@deleteOption'
]);
$router->get('/service/{service}/option/{option}/variable/new', [
'as' => 'admin.services.option.variable.new',
'uses' => 'Admin\ServiceController@getNewVariable'
]);
$router->post('/service/{service}/option/{option}/variable/new', [
'uses' => 'Admin\ServiceController@postNewVariable'
]);
$router->post('/service/{service}/option/{option}/variable/{variable}', [
'as' => 'admin.services.option.variable',
'uses' => 'Admin\ServiceController@postOptionVariable'
]);
$router->get('/service/{service}/option/{option}/variable/{variable}/delete', [
'as' => 'admin.services.option.variable.delete',
'uses' => 'Admin\ServiceController@deleteVariable'
]);
});
}
}

View file

@ -71,15 +71,19 @@ class BaseRoutes {
// TOTP Routes
$router->group([
'prefix' => 'account/totp',
'prefix' => 'account/security',
'middleware' => [
'auth',
'csrf'
]
], function () use ($router) {
$router->get('/', [
'as' => 'account.totp',
'uses' => 'Base\IndexController@getAccountTotp'
'as' => 'account.security',
'uses' => 'Base\IndexController@getAccountSecurity'
]);
$router->get('/revoke/{id}', [
'as' => 'account.security.revoke',
'uses' => 'Base\IndexController@getRevokeSession'
]);
$router->put('/', [
'uses' => 'Base\IndexController@putAccountTotp'

View file

@ -35,4 +35,11 @@ class Service extends Model
*/
protected $table = 'services';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
}

View file

@ -35,6 +35,13 @@ class ServiceOptions extends Model
*/
protected $table = 'service_options';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*

View file

@ -35,6 +35,13 @@ class ServiceVariables extends Model
*/
protected $table = 'service_variables';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*

48
app/Models/Session.php Normal file
View file

@ -0,0 +1,48 @@
<?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 Session extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'sessions';
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'id' => 'string',
'user_id' => 'integer',
];
}

View file

@ -122,27 +122,27 @@ class APIRepository
DB::beginTransaction();
$secretKey = str_random(16) . '.' . str_random(15);
$key = new Models\APIKey;
$key->fill([
'public' => str_random(16),
'secret' => Crypt::encrypt($secretKey),
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed)
]);
$key->save();
foreach($data['permissions'] as $permission) {
if (in_array($permission, $this->permissions)) {
$model = new Models\APIPermission;
$model->fill([
'key_id' => $key->id,
'permission' => $permission
]);
$model->save();
}
}
try {
$secretKey = str_random(16) . '.' . str_random(15);
$key = new Models\APIKey;
$key->fill([
'public' => str_random(16),
'secret' => Crypt::encrypt($secretKey),
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed)
]);
$key->save();
foreach($data['permissions'] as $permission) {
if (in_array($permission, $this->permissions)) {
$model = new Models\APIPermission;
$model->fill([
'key_id' => $key->id,
'permission' => $permission
]);
$model->save();
}
}
DB::commit();
return $secretKey;
} catch (\Exception $ex) {
@ -164,11 +164,16 @@ class APIRepository
{
DB::beginTransaction();
$model = Models\APIKey::where('public', $key)->firstOrFail();
$permissions = Models\APIPermission::where('key_id', $model->id)->delete();
$model->delete();
try {
$model = Models\APIKey::where('public', $key)->firstOrFail();
$permissions = Models\APIPermission::where('key_id', $model->id)->delete();
$model->delete();
DB::commit();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
}

View file

@ -151,51 +151,52 @@ class NodeRepository {
$node = Models\Node::findOrFail($id);
DB::beginTransaction();
foreach($allocations as $rawIP => $ports) {
$parsedIP = Network::parse($rawIP);
foreach($parsedIP as $ip) {
foreach($ports as $port) {
if (!is_int($port) && !preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) {
throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.');
}
if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) {
foreach(range($matches[1], $matches[2]) as $assignPort) {
try {
foreach($allocations as $rawIP => $ports) {
$parsedIP = Network::parse($rawIP);
foreach($parsedIP as $ip) {
foreach($ports as $port) {
if (!is_int($port) && !preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) {
throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.');
}
if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) {
foreach(range($matches[1], $matches[2]) as $assignPort) {
$alloc = Models\Allocation::firstOrNew([
'node' => $node->id,
'ip' => $ip,
'port' => $assignPort
]);
if (!$alloc->exists) {
$alloc->fill([
'node' => $node->id,
'ip' => $ip,
'port' => $assignPort,
'assigned_to' => null
]);
$alloc->save();
}
}
} else {
$alloc = Models\Allocation::firstOrNew([
'node' => $node->id,
'ip' => $ip,
'port' => $assignPort
'port' => $port
]);
if (!$alloc->exists) {
$alloc->fill([
'node' => $node->id,
'ip' => $ip,
'port' => $assignPort,
'port' => $port,
'assigned_to' => null
]);
$alloc->save();
}
}
} else {
$alloc = Models\Allocation::firstOrNew([
'node' => $node->id,
'ip' => $ip,
'port' => $port
]);
if (!$alloc->exists) {
$alloc->fill([
'node' => $node->id,
'ip' => $ip,
'port' => $port,
'assigned_to' => null
]);
$alloc->save();
}
}
}
}
}
try {
DB::commit();
return true;
} catch (\Exception $ex) {

View file

@ -90,11 +90,11 @@ class ServerRepository
'owner' => 'required|email|exists:users,email',
'node' => 'required|numeric|min:1|exists:nodes,id',
'name' => 'required|regex:/^([\w -]{4,35})$/',
'memory' => 'required|numeric|min:1',
'swap' => 'required|numeric|min:0',
'disk' => 'required|numeric|min:1',
'cpu' => 'required|numeric|min:0',
'memory' => 'required|numeric|min:0',
'swap' => 'required|numeric|min:-1',
'io' => 'required|numeric|min:10|max:1000',
'cpu' => 'required|numeric|min:0',
'disk' => 'required|numeric|min:0',
'ip' => 'required|ip',
'port' => 'required|numeric|min:1|max:65535',
'service' => 'required|numeric|min:1|exists:services,id',
@ -194,55 +194,54 @@ class ServerRepository
DB::beginTransaction();
$uuid = new UuidService;
// Add Server to the Database
$server = new Models\Server;
$generatedUuid = $uuid->generate('servers', 'uuid');
$server->fill([
'uuid' => $generatedUuid,
'uuidShort' => $uuid->generateShort('servers', 'uuidShort', $generatedUuid),
'node' => $data['node'],
'name' => $data['name'],
'active' => 1,
'owner' => $user->id,
'memory' => $data['memory'],
'swap' => $data['swap'],
'disk' => $data['disk'],
'io' => $data['io'],
'cpu' => $data['cpu'],
'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
'ip' => $data['ip'],
'port' => $data['port'],
'service' => $data['service'],
'option' => $data['option'],
'startup' => $data['startup'],
'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
'username' => $this->generateSFTPUsername($data['name'])
]);
$server->save();
// Mark Allocation in Use
$allocation->assigned_to = $server->id;
$allocation->save();
// Add Variables
$environmentVariables = [];
$environmentVariables = array_merge($environmentVariables, [
'STARTUP' => $data['startup']
]);
foreach($variableList as $item) {
$environmentVariables = array_merge($environmentVariables, [
$item['env'] => $item['val']
]);
Models\ServerVariables::create([
'server_id' => $server->id,
'variable_id' => $item['id'],
'variable_value' => $item['val']
]);
}
try {
$uuid = new UuidService;
// Add Server to the Database
$server = new Models\Server;
$generatedUuid = $uuid->generate('servers', 'uuid');
$server->fill([
'uuid' => $generatedUuid,
'uuidShort' => $uuid->generateShort('servers', 'uuidShort', $generatedUuid),
'node' => $data['node'],
'name' => $data['name'],
'active' => 1,
'owner' => $user->id,
'memory' => $data['memory'],
'swap' => $data['swap'],
'disk' => $data['disk'],
'io' => $data['io'],
'cpu' => $data['cpu'],
'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
'ip' => $data['ip'],
'port' => $data['port'],
'service' => $data['service'],
'option' => $data['option'],
'startup' => $data['startup'],
'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
'username' => $this->generateSFTPUsername($data['name'])
]);
$server->save();
// Mark Allocation in Use
$allocation->assigned_to = $server->id;
$allocation->save();
// Add Variables
$environmentVariables = [];
$environmentVariables = array_merge($environmentVariables, [
'STARTUP' => $data['startup']
]);
foreach($variableList as $item) {
$environmentVariables = array_merge($environmentVariables, [
$item['env'] => $item['val']
]);
Models\ServerVariables::create([
'server_id' => $server->id,
'variable_id' => $item['id'],
'variable_value' => $item['val']
]);
}
$client = Models\Node::guzzleRequest($node->id);
$client->request('POST', '/servers', [
@ -317,39 +316,39 @@ class ServerRepository
}
DB::beginTransaction();
$server = Models\Server::findOrFail($id);
$owner = Models\User::findOrFail($server->owner);
// Update daemon secret if it was passed.
if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) {
$oldDaemonKey = $server->daemonSecret;
$server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
$resetDaemonKey = true;
}
// Update Server Owner if it was passed.
if (isset($data['owner']) && $data['owner'] !== $owner->email) {
$newOwner = Models\User::select('id')->where('email', $data['owner'])->first();
$server->owner = $newOwner->id;
}
// Update Server Name if it was passed.
if (isset($data['name'])) {
$server->name = $data['name'];
}
// Save our changes
$server->save();
// Do we need to update? If not, return successful.
if (!$resetDaemonKey) {
DB::commit();
return true;
}
// If we need to update do it here.
try {
$server = Models\Server::findOrFail($id);
$owner = Models\User::findOrFail($server->owner);
// Update daemon secret if it was passed.
if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) {
$oldDaemonKey = $server->daemonSecret;
$server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
$resetDaemonKey = true;
}
// Update Server Owner if it was passed.
if (isset($data['owner']) && $data['owner'] !== $owner->email) {
$newOwner = Models\User::select('id')->where('email', $data['owner'])->first();
$server->owner = $newOwner->id;
}
// Update Server Name if it was passed.
if (isset($data['name'])) {
$server->name = $data['name'];
}
// Save our changes
$server->save();
// Do we need to update? If not, return successful.
if (!$resetDaemonKey) {
DB::commit();
return true;
}
// If we need to update do it here.
$node = Models\Node::getByID($server->node);
$client = Models\Node::guzzleRequest($server->node);
@ -411,97 +410,97 @@ class ServerRepository
}
DB::beginTransaction();
$server = Models\Server::findOrFail($id);
if (isset($data['default'])) {
list($ip, $port) = explode(':', $data['default']);
if ($ip !== $server->ip || $port !== $server->port) {
$allocation = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first();
if (!$allocation) {
throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.');
}
$server->ip = $ip;
$server->port = $port;
}
}
// Remove Assignments
if (isset($data['remove_additional'])) {
foreach ($data['remove_additional'] as $id => $combo) {
list($ip, $port) = explode(':', $combo);
// Invalid, not worth killing the whole thing, we'll just skip over it.
if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) {
continue;
}
// Can't remove the assigned IP/Port combo
if ($ip === $server->ip && $port === $server->port) {
continue;
}
Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([
'assigned_to' => null
]);
}
}
// Add Assignments
if (isset($data['add_additional'])) {
foreach ($data['add_additional'] as $id => $combo) {
list($ip, $port) = explode(':', $combo);
// Invalid, not worth killing the whole thing, we'll just skip over it.
if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) {
continue;
}
// Don't allow double port assignments
if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) {
continue;
}
Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([
'assigned_to' => $server->id
]);
}
}
// Loop All Assignments
$additionalAssignments = [];
$assignments = Models\Allocation::where('assigned_to', $server->id)->get();
foreach ($assignments as &$assignment) {
if (array_key_exists((string) $assignment->ip, $additionalAssignments)) {
array_push($additionalAssignments[ (string) $assignment->ip ], (int) $assignment->port);
} else {
$additionalAssignments[ (string) $assignment->ip ] = [ (int) $assignment->port ];
}
}
// @TODO: verify that server can be set to this much memory without
// going over node limits.
if (isset($data['memory'])) {
$server->memory = $data['memory'];
}
if (isset($data['swap'])) {
$server->swap = $data['swap'];
}
// @TODO: verify that server can be set to this much disk without
// going over node limits.
if (isset($data['disk'])) {
$server->disk = $data['disk'];
}
if (isset($data['cpu'])) {
$server->cpu = $data['cpu'];
}
if (isset($data['io'])) {
$server->io = $data['io'];
}
try {
$server = Models\Server::findOrFail($id);
if (isset($data['default'])) {
list($ip, $port) = explode(':', $data['default']);
if ($ip !== $server->ip || $port !== $server->port) {
$allocation = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first();
if (!$allocation) {
throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.');
}
$server->ip = $ip;
$server->port = $port;
}
}
// Remove Assignments
if (isset($data['remove_additional'])) {
foreach ($data['remove_additional'] as $id => $combo) {
list($ip, $port) = explode(':', $combo);
// Invalid, not worth killing the whole thing, we'll just skip over it.
if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) {
continue;
}
// Can't remove the assigned IP/Port combo
if ($ip === $server->ip && $port === $server->port) {
continue;
}
Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([
'assigned_to' => null
]);
}
}
// Add Assignments
if (isset($data['add_additional'])) {
foreach ($data['add_additional'] as $id => $combo) {
list($ip, $port) = explode(':', $combo);
// Invalid, not worth killing the whole thing, we'll just skip over it.
if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) {
continue;
}
// Don't allow double port assignments
if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) {
continue;
}
Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([
'assigned_to' => $server->id
]);
}
}
// Loop All Assignments
$additionalAssignments = [];
$assignments = Models\Allocation::where('assigned_to', $server->id)->get();
foreach ($assignments as &$assignment) {
if (array_key_exists((string) $assignment->ip, $additionalAssignments)) {
array_push($additionalAssignments[ (string) $assignment->ip ], (int) $assignment->port);
} else {
$additionalAssignments[ (string) $assignment->ip ] = [ (int) $assignment->port ];
}
}
// @TODO: verify that server can be set to this much memory without
// going over node limits.
if (isset($data['memory'])) {
$server->memory = $data['memory'];
}
if (isset($data['swap'])) {
$server->swap = $data['swap'];
}
// @TODO: verify that server can be set to this much disk without
// going over node limits.
if (isset($data['disk'])) {
$server->disk = $data['disk'];
}
if (isset($data['cpu'])) {
$server->cpu = $data['cpu'];
}
if (isset($data['io'])) {
$server->io = $data['io'];
}
$node = Models\Node::getByID($server->node);
$client = Models\Node::guzzleRequest($server->node);
@ -534,7 +533,6 @@ class ServerRepository
throw new DisplayException('An error occured while attempting to update the configuration: ' . $ex->getMessage());
} catch (\Exception $ex) {
DB::rollBack();
Log::error($ex);
throw $ex;
}
@ -547,21 +545,20 @@ class ServerRepository
DB::beginTransaction();
// Check the startup
if (isset($data['startup'])) {
$server->startup = $data['startup'];
$server->save();
}
// Check those Variables
$variables = Models\ServiceVariables::select(
'service_variables.*',
DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue')
)->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('option_id', $server->option)
->get();
try {
// Check the startup
if (isset($data['startup'])) {
$server->startup = $data['startup'];
$server->save();
}
// Check those Variables
$variables = Models\ServiceVariables::select(
'service_variables.*',
DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue')
)->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('option_id', $server->option)
->get();
$variableList = [];
if ($variables) {
@ -664,24 +661,24 @@ class ServerRepository
$node = Models\Node::findOrFail($server->node);
DB::beginTransaction();
// Delete Allocations
Models\Allocation::where('assigned_to', $server->id)->update([
'assigned_to' => null
]);
// Remove Variables
Models\ServerVariables::where('server_id', $server->id)->delete();
// Remove SubUsers
Models\Subuser::where('server_id', $server->id)->delete();
// Remove Permissions
Models\Permission::where('server_id', $server->id)->delete();
// Remove Downloads
Models\Download::where('server', $server->uuid)->delete();
try {
// Delete Allocations
Models\Allocation::where('assigned_to', $server->id)->update([
'assigned_to' => null
]);
// Remove Variables
Models\ServerVariables::where('server_id', $server->id)->delete();
// Remove SubUsers
Models\Subuser::where('server_id', $server->id)->delete();
// Remove Permissions
Models\Permission::where('server_id', $server->id)->delete();
// Remove Downloads
Models\Download::where('server', $server->uuid)->delete();
$client = Models\Node::guzzleRequest($server->node);
$client->request('DELETE', '/servers', [
'headers' => [

View file

@ -0,0 +1,127 @@
<?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 Validator;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Option
{
public function __construct()
{
//
}
public function create($service, array $data)
{
$service = Models\Service::findOrFail($service);
$validator = Validator::make($data, [
'name' => 'required|string|max:255',
'description' => 'required|string|min:1',
'tag' => 'required|string|max:255',
'executable' => 'sometimes|string|max:255',
'docker_image' => 'required|string|max:255',
'startup' => 'sometimes|string'
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['executable']) && empty($data['executable'])) {
$data['executable'] = null;
}
if (isset($data['startup']) && empty($data['startup'])) {
$data['startup'] = null;
}
$option = new Models\ServiceOptions;
$option->parent_service = $service->id;
$option->fill($data);
$option->save();
return $option->id;
}
public function delete($id)
{
$option = Models\ServiceOptions::findOrFail($id);
$servers = Models\Server::where('option', $option->id)->get();
if (count($servers) !== 0) {
throw new DisplayException('You cannot delete an option that has servers attached to it currently.');
}
DB::beginTransaction();
try {
Models\ServiceVariables::where('option_id', $option->id)->delete();
$option->delete();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function update($id, array $data)
{
$option = Models\ServiceOptions::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|max:255',
'description' => 'sometimes|required|string|min:1',
'tag' => 'sometimes|required|string|max:255',
'executable' => 'sometimes|string|max:255',
'docker_image' => 'sometimes|required|string|max:255',
'startup' => 'sometimes|string'
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['executable']) && empty($data['executable'])) {
$data['executable'] = null;
}
if (isset($data['startup']) && empty($data['startup'])) {
$data['startup'] = null;
}
$option->fill($data);
$option->save();
}
}

View file

@ -0,0 +1,110 @@
<?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 Validator;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Service
{
public function __construct()
{
//
}
public function create(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|string',
'file' => 'required|regex:/^[\w.-]{1,50}$/',
'executable' => 'required|max:255|regex:/^(.*)$/',
'startup' => 'required|string'
]);
if ($validator->fails()) {
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.');
}
$service = new Models\Service;
$service->fill($data);
$service->save();
return $service->id;
}
public function update($id, array $data)
{
$service = Models\Service::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|string',
'file' => 'sometimes|required|regex:/^[\w.-]{1,50}$/',
'executable' => 'sometimes|required|max:255|regex:/^(.*)$/',
'startup' => 'sometimes|required|string'
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$service->fill($data);
$service->save();
}
public function delete($id)
{
$service = Models\Service::findOrFail($id);
$servers = Models\Server::where('service', $service->id)->get();
$options = Models\ServiceOptions::select('id')->where('parent_service', $service->id);
if (count($servers) !== 0) {
throw new DisplayException('You cannot delete a service that has servers associated with it.');
}
DB::beginTransaction();
try {
Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete();
$options->delete();
$service->delete();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
}

View file

@ -0,0 +1,133 @@
<?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 Validator;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Variable
{
public function __construct()
{
//
}
public function create($id, array $data)
{
$option = Models\ServiceOptions::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/',
'default_value' => 'required|string|max:255',
'user_viewable' => 'sometimes|required|numeric|size:1',
'user_editable' => 'sometimes|required|numeric|size:1',
'required' => 'sometimes|required|numeric|size:1',
'regex' => 'required|string|min:1'
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (!preg_match($data['regex'], $data['default_value'])) {
throw new DisplayException('The default value you entered cannot violate the regex requirements.');
}
if (Models\ServiceVariables::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.');
}
$data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0;
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0;
$variable = new Models\ServiceVariables;
$variable->option_id = $option->id;
$variable->fill($data);
$variable->save();
}
public function delete($id) {
$variable = Models\ServiceVariables::findOrFail($id);
DB::beginTransaction();
try {
Models\ServerVariables::where('variable_id', $variable->id)->delete();
$variable->delete();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function update($id, array $data)
{
$variable = Models\ServiceVariables::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|string',
'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/',
'default_value' => 'sometimes|required|string|max:255',
'user_viewable' => 'sometimes|required|numeric|size:1',
'user_editable' => 'sometimes|required|numeric|size:1',
'required' => 'sometimes|required|numeric|size:1',
'regex' => 'sometimes|required|string|min:1'
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$data['default_value'] = (isset($data['default_value'])) ? $data['default_value'] : $variable->default_value;
$data['regex'] = (isset($data['regex'])) ? $data['regex'] : $variable->regex;
if (!preg_match($data['regex'], $data['default_value'])) {
throw new DisplayException('The default value you entered cannot violate the regex requirements.');
}
if (Models\ServiceVariables::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.');
}
$data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : $variable->user_viewable;
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required;
$variable->fill($data);
$variable->save();
}
}

View file

@ -69,19 +69,19 @@ class UserRepository
throw new DisplayValidationException($validator->errors());
}
$user = new Models\User;
$uuid = new UuidService;
DB::beginTransaction();
$user->uuid = $uuid->generate('users', 'uuid');
$user->email = $email;
$user->password = Hash::make($password);
$user->language = 'en';
$user->root_admin = ($admin) ? 1 : 0;
$user->save();
try {
$user = new Models\User;
$uuid = new UuidService;
$user->uuid = $uuid->generate('users', 'uuid');
$user->email = $email;
$user->password = Hash::make($password);
$user->language = 'en';
$user->root_admin = ($admin) ? 1 : 0;
$user->save();
Mail::queue('emails.new-account', [
'email' => $user->email,
'forgot' => route('auth.password'),
@ -108,13 +108,15 @@ class UserRepository
*/
public function update($id, array $data)
{
$user = Models\User::findOrFail($id);
$validator = Validator::make($data, [
'email' => 'email|unique:users,email,' . $id,
'password' => 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
'root_admin' => 'boolean',
'language' => 'string|min:1|max:5',
'use_totp' => 'boolean',
'totp_secret' => 'size:16'
'email' => 'sometimes|required|email|unique:users,email,' . $id,
'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
'root_admin' => 'sometimes|required|boolean',
'language' => 'sometimes|required|string|min:1|max:5',
'use_totp' => 'sometimes|required|boolean',
'totp_secret' => 'sometimes|required|size:16'
]);
// Run validator, throw catchable and displayable exception if it fails.
@ -127,7 +129,12 @@ class UserRepository
$data['password'] = Hash::make($data['password']);
}
return Models\User::findOrFail($id)->update($data);
if (isset($data['password_confirmation'])) {
unset($data['password_confirmation']);
}
$user->fill($data);
$user->save();
}
/**
@ -144,14 +151,15 @@ class UserRepository
DB::beginTransaction();
Models\Permission::where('user_id', $id)->delete();
Models\Subuser::where('user_id', $id)->delete();
Models\User::destroy($id);
try {
Models\Permission::where('user_id', $id)->delete();
Models\Subuser::where('user_id', $id)->delete();
Models\User::destroy($id);
DB::commit();
return true;
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}

View file

@ -4,7 +4,7 @@ return [
'env' => env('APP_ENV', 'production'),
'version' => env('APP_VERSION', 'v0.2.0-beta'),
'version' => env('APP_VERSION', 'v0.3.0-beta'),
/*
|--------------------------------------------------------------------------
@ -30,7 +30,7 @@ return [
|
*/
'url' => ENV('APP_URL', 'http://localhost'),
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
@ -43,7 +43,7 @@ return [
|
*/
'timezone' => ENV('APP_TIMEZONE', 'UTC'),
'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddUniqueServiceField extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('services', function (Blueprint $table) {
$table->string('file')->unique()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('services', function (Blueprint $table) {
$table->dropUnique('services_file_unique');
});
}
}

2
public/js/async.min.js vendored Executable file

File diff suppressed because one or more lines are too long

1
public/js/async.min.map Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/jquery.min.map Normal file

File diff suppressed because one or more lines are too long

View file

@ -44,7 +44,6 @@ return [
'no_servers' => 'You do not currently have any servers listed on your account.',
'form_error' => 'The following errors were encountered while trying to process this request.',
'password_req' => 'Passwords must meet the following requirements: at least one uppercase character, one lowercase character, one digit, and be at least 8 characters in length.',
'root_administrator' => 'Setting this to "Yes" gives a user full administrative access to PufferPanel.',
'account' => [
'totp_header' => 'Two-Factor Authentication',

View file

@ -1,173 +0,0 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Viewing User
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Controls</a></li>
<li><a href="/admin/accounts">Accounts</a></li>
<li class="active">{{ $user->email }}</li>
</ul>
<h3>Viewing User: {{ $user->email }}</h3><hr />
<div class="row">
<div class="col-md-12">
<form action="/admin/accounts/update" method="post">
<div class="col-md-6">
<fieldset>
<div class="form-group">
<label for="email" class="control-label">{{ trans('strings.email') }}</label>
<div>
<input type="text" name="email" value="{{ $user->email }}" class="form-control">
</div>
</div>
<div class="form-group">
<label for="registered" class="control-label">{{ trans('strings.registered') }}</label>
<div>
<input type="text" name="registered" value="{{ $user->created_at }}" readonly="readonly" class="form-control">
</div>
</div>
<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><small class="text-muted"><em><strong><i class="fa fa-warning"></i></strong> {{ trans('base.root_administrator') }}</em></small></p>
</div>
</div>
<div class="form-group">
<input type="hidden" name="user" value="{{ $user->id }}">
{!! csrf_field() !!}
<input type="submit" value="{{ trans('base.account.update_user') }}" class="btn btn-primary btn-sm">
<a href="#">
<button type="button" class="btn btn-sm btn-danger" data-action="deleteUser" value="{{ trans('base.account.delete_user') }}">{{ trans('base.account.delete_user') }}</button>
</a>
</div>
</fieldset>
</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>
<div>
<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 class="checkbox">
<label><input type="checkbox" name="email_user" value="1">{{ trans('base.account.email_password') }}</label>
</div>
</div>
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>Associated Servers</h3><hr>
@if($servers)
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th style="width:2%;"></th>
<th>Server Name</th>
<th>Node</th>
<th>Connection</th>
<th style="width:10%;"></th>
</tr>
</thead>
<tbody>
@foreach($servers as $server)
<tr>
<td><a href="/server/{{ $server->uuidShort }}/"><i class="fa fa-tachometer"></i></a></td>
<td><a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</a></td>
<td>{{ $server->nodeName }}</td>
<td><code>{{ $server->ip }}:{{ $server->port }}</code></td>
<td>@if($server->active)<span class="label label-success">Enabled</span>@else<span class="label label-danger">Disabled</span>@endif</td>
</td>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info">There are no servers associated with this account.</div>
@endif
<a href="/admin/servers/new?email={{ $user->email }}"><button type="button" class="btn btn-success btn-sm">{{ trans('server.index.add_new') }}</button></a>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$("#sidebar_links").find("a[href='/admin/accounts']").addClass('active');
$('#delete').click(function() {
if(confirm('{{ trans('base.confirm') }}')) {
$('#delete').load($(this).attr('href'));
}
});
$("#gen_pass_bttn").click(function(e){
e.preventDefault();
$.ajax({
type: "GET",
url: "/password-gen/12",
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
success: function(data) {
$("#gen_pass").html('<strong>Generated Password:</strong> ' + data).slideDown();
$('input[name="password"], input[name="password_confirmation"]').val(data);
return false;
}
});
return false;
});
$('button[data-action="deleteUser"]').click(function (event) {
event.preventDefault();
$.ajax({
method: 'DELETE',
url: '/admin/accounts/view/{{ $user->id }}',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
}).done(function (data) {
alert('Account was successfully deleted from the system.');
window.location = '/admin/accounts';
}).fail(function (jqXHR) {
console.error(jqXHR);
alert('An error occured: ' + jqXHR.JSONResponse.error);
})
})
});
</script>
@endsection

View file

@ -44,7 +44,7 @@
@foreach ($servers as $server)
<tr class="dynUpdate @if($server->active !== 1)active @endif" id="{{ $server->uuidShort }}">
<td><a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</td>
<td><a href="/admin/accounts/view/{{ $server->owner }}">{{ $server->a_ownerEmail }}</a></td>
<td><a href="/admin/users/view/{{ $server->owner }}">{{ $server->a_ownerEmail }}</a></td>
<td class="hidden-xs"><a href="/admin/nodes/view/{{ $server->node }}">{{ $server->a_nodeName }}</a></td>
<td><code>{{ $server->ip }}:{{ $server->port }}</code></td>
<td class="hidden-xs"><code>{{ $server->username }}</code></td>

View file

@ -65,7 +65,7 @@
</tr>
<tr>
<td>Owner</td>
<td><a href="{{ route('admin.accounts.view', $server->owner) }}">{{ $server->a_ownerEmail }}</a></td>
<td><a href="{{ route('admin.users.view', $server->owner) }}">{{ $server->a_ownerEmail }}</a></td>
</tr>
<tr>
<td>Location</td>

View file

@ -0,0 +1,62 @@
{{-- 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 Services
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li class="active">Services</li>
</ul>
<h3 class="nopad">Server Services</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Service Type</th>
<th>Description</th>
<th class="text-center">Servers</th>
</tr>
</thead>
<tbody>
@foreach ($services as $service)
<tr>
<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>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td class="text-center"><a href="{{ route('admin.services.new') }}"><i class="fa fa-plus"></i></a></td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
});
</script>
@endsection

View file

@ -0,0 +1,95 @@
{{-- 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')
New Service
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li class="active">New Service</li>
</ul>
<h3 class="nopad">Add New Service</h3><hr />
<form action="{{ route('admin.services.new') }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name') }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Service Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Configuration File:</label>
<div class="input-group">
<span class="input-group-addon">/src/services/</span>
<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>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Display Executable:</label>
<div>
<input type="text" name="executable" class="form-control" value="{{ old('executable') }}" />
</div>
<p class="text-muted"><small>Changing this has no effect on operation of the daemon, it is simply used for display purposes on the panel. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup:</label>
<div class="input-group">
<span class="input-group-addon" id="disp_exec"></span>
<input type="text" name="startup" class="form-control" value="{{ old('startup') }}" />
</div>
<p class="text-muted"><small>This is the default startup that will be used for all servers created using this service. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">You will be able to add service options and variables once the service is created.</div>
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add New Service" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/new']").addClass('active');
$('input[name="executable"]').on('keyup', function() {
$("#disp_exec").html(escape($(this).val()));
});
});
</script>
@endsection

View file

@ -0,0 +1,98 @@
{{-- 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')
New Service Option 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.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">New Service Option</li>
</ul>
<h3>Service Option Settings</h3><hr />
<form action="{{ route('admin.services.option.new', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Name:</label>
<div>
<input type="text" name="name" value="{{ old('name') }}" class="form-control" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label class="control-label">Tag:</label>
<div>
<input type="text" name="tag" value="{{ old('tag') }}" class="form-control" />
</div>
</div>
<div class="col-md-3 form-group">
<label class="control-label">Executable:</label>
<div>
<input type="text" name="executable" value="{{ old('executable') }}" class="form-control" />
<p class="text-muted"><small>Leave blank to use parent executable.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Docker Image:</label>
<div>
<input type="text" name="docker_image" value="{{ old('docker_image') }}" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup Command:</label>
<div>
<input type="text" name="startup" value="{{ old('startup') }}" class="form-control" />
<p class="text-muted"><small>To use the default startup of the parent service simply leave this field blank.</small></p>
</div>
</div>
</div>
<div class="well well-sm">
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Create Service Option" />
</div>
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View file

@ -0,0 +1,122 @@
{{-- 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')
New Variable 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.service', $service->id) }}">{{ $service->name }}</a></li>
<li><a href="{{ route('admin.services.option', [$service->id, $option->id]) }}">{{ $option->name }}</a></li>
<li class="active">New Variable</li>
</ul>
<h3>New Option Variable</h3><hr />
<form action="{{ route('admin.services.option.variable.new', [$service->id, $option->id]) }}" method="POST">
<div class="well">
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Variable Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name') }}" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Variable Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Regex:</label>
<div>
<input type="text" name="regex" class="form-control" value="{{ old('regex') }}" />
<p class="text-muted"><small>Regex code to use when verifying the contents of the field.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Environment Variable:</label>
<div>
<input type="text" name="env_variable" id="env_var" class="form-control" value="{{ old('env_variable') }}" />
<p class="text-muted"><small>Accessed in startup by using <code>&#123;&#123;&#125;&#125;</code> parameter.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Default Value:</label>
<div>
<input type="text" name="default_value" class="form-control" value="{{ old('default_value') }}" />
<p class="text-muted"><small>The default value to use for this field.</small></p>
</div>
</div>
</div>
<div class="row fuelux">
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="user_viewable" type="checkbox" value="1" @if((int) old('user_viewable') === 1)checked="checked"@endif> <strong>User Viewable</strong>
<p class="text-muted"><small>Can users view this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="user_editable" type="checkbox" value="1" @if((int) old('user_editable') === 1)checked="checked"@endif> <strong>User Editable</strong>
<p class="text-muted"><small>Can users edit this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="required" type="checkbox" value="1" @if((int) old('required') === 1)checked="checked"@endif> <strong>Required</strong>
<p class="text-muted"><small>This this variable required?</small><p>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add Variable" />
</div>
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View file

@ -0,0 +1,210 @@
{{-- 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 Option {{ $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.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">{{ $option->name }}</li>
</ul>
<div class="alert alert-warning"><strong>Warning!</strong> This page contains advanced settings that the panel and daemon use to control servers. Modifying information on this page is not recommended unless you are absolutely sure of what you are doing.</div>
<h3>Settings</h3><hr />
<form action="{{ route('admin.services.option', [$service->id, $option->id]) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Name:</label>
<div>
<input type="text" name="name" value="{{ old('name', $option->name) }}" class="form-control" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description', $option->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label class="control-label">Tag:</label>
<div>
<input type="text" name="tag" value="{{ old('tag', $option->tag) }}" class="form-control" />
</div>
</div>
<div class="col-md-3 form-group">
<label class="control-label">Executable:</label>
<div>
<input type="text" name="executable" value="{{ old('executable', $option->executable) }}" class="form-control" />
<p class="text-muted"><small>Leave blank to use parent executable.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Docker Image:</label>
<div>
<input type="text" name="docker_image" value="{{ old('docker_image', $option->docker_image) }}" class="form-control" />
<p class="text-muted"><small>Changing the docker image will only effect servers created or modified after this point.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup Command:</label>
<div>
<input type="text" name="startup" value="{{ old('startup', $option->startup) }}" class="form-control" />
<p class="text-muted"><small>To use the default startup of the parent service simply leave this field blank.</small></p>
</div>
</div>
</div>
<div class="well well-sm">
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Update Service Option" />
</div>
</div>
</div>
</form>
<h3>Variables <small><a href="{{ route('admin.services.option.variable.new', [$service->id, $option->id]) }}"><i class="fa fa-plus"></i></a></small></h3><hr />
@foreach($variables as $variable)
<form action="{{ route('admin.services.option.variable', [$service->id, $option->id, $variable->id]) }}" method="POST">
<div class="well">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Variable Name:</label>
<div>
<input type="text" name="{{ $variable->id }}_name" class="form-control" value="{{ old($variable->id.'_name', $variable->name) }}" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Variable Description:</label>
<div>
<textarea name="{{ $variable->id }}_description" class="form-control" rows="2">{{ old($variable->id.'_description', $variable->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 form-group">
<label class="control-label">Environment Variable:</label>
<div>
<input type="text" name="{{ $variable->id }}_env_variable" id="env_var" class="form-control" value="{{ old($variable->id.'_env_variable', $variable->env_variable) }}" />
<p class="text-muted"><small>Accessed in startup by using <code>&#123;&#123;{{ $variable->env_variable }}&#125;&#125;</code> prameter.</small></p>
</div>
</div>
<div class="col-md-4 form-group">
<label class="control-label">Default Value:</label>
<div>
<input type="text" name="{{ $variable->id }}_default_value" class="form-control" value="{{ old($variable->id.'_default_value', $variable->default_value) }}" />
<p class="text-muted"><small>The default value to use for this field.</small></p>
</div>
</div>
<div class="col-md-4 form-group">
<label class="control-label">Regex:</label>
<div>
<input type="text" name="{{ $variable->id }}_regex" class="form-control" value="{{ old($variable->id.'_regex', $variable->regex) }}" />
<p class="text-muted"><small>Regex code to use when verifying the contents of the field.</small></p>
</div>
</div>
</div>
<div class="row fuelux">
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_user_viewable" type="checkbox" value="1" @if((int) old($variable->id.'_user_viewable', $variable->user_viewable) === 1)checked="checked"@endif> <strong>User Viewable</strong>
<p class="text-muted"><small>Can users view this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_user_editable" type="checkbox" value="1" @if((int) old($variable->id.'_user_editable', $variable->user_editable) === 1)checked="checked"@endif> <strong>User Editable</strong>
<p class="text-muted"><small>Can users edit this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_required" type="checkbox" value="1" @if((int) old($variable->id.'_required', $variable->required) === 1)checked="checked"@endif> <strong>Required</strong>
<p class="text-muted"><small>This this variable required?</small><p>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<a href="{{ route('admin.services.option.variable.delete', [$service->id, $option->id, $variable->id]) }}"><button type="button" class="btn btn-sm btn-danger pull-right"><i class="fa fa-times"></i></button></a>
<input type="submit" class="btn btn-sm btn-success" value="Update Variable" />
</div>
</div>
</div>
</form>
@endforeach
<h3>Servers</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
<th>Connection</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
@foreach ($servers as $server)
<tr>
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
<td><a href="{{ route('admin.users.view', $server->owner) }}">{{ $server->a_ownerEmail }}</a></td>
<td><code>{{ $server->ip }}:{{ $server->port }}</code></td>
<td>{{ $server->updated_at }}</td>
</tr>
@endforeach
</tbody>
</table>
<form action="{{ route('admin.services.option', [$service->id, $option->id]) }}" method="POST">
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger">
Deleting an option is an irreversible action. An option can <em>only</em> be deleted if no servers are associated with it.
</div>
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete Option" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View file

@ -0,0 +1,137 @@
{{-- 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
@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">{{ $service->name }}</li>
</ul>
<h3 class="nopad">Service Options</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Option Name</th>
<th>Description</th>
<th>Docker Image</th>
<th>Tag</th>
<th class="text-center">Servers</th>
</tr>
</thead>
<tbody>
@foreach($options as $option)
<tr>
<td><a href="{{ route('admin.services.option', [ $service->id, $option->id]) }}">{{ $option->name }}</a></td>
<td>{!! $option->description !!}</td>
<td><code>{{ $option->docker_image }}</code></td>
<td><code>{{ $option->tag }}</code></td>
<td class="text-center">{{ $option->c_servers }}</td>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-center"><a href="{{ route('admin.services.option.new', $service->id) }}"><i class="fa fa-plus"></i></a></td>
</tr>
</tbody>
</table>
<div class="well">
<form action="{{ route('admin.services.service', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name', $service->name) }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Service Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description', $service->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Configuration File:</label>
<div class="input-group">
<span class="input-group-addon">/src/services/</span>
<input type="text" name="file" class="form-control" value="{{ old('file', $service->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. Changing this can have unintended effects on servers or causes errors to occur.</small></p>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Display Executable:</label>
<div>
<input type="text" name="executable" class="form-control" value="{{ old('executable', $service->executable) }}" />
</div>
<p class="text-muted"><small>Changing this has no effect on operation of the daemon, it is simply used for display purposes on the panel. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup:</label>
<div class="input-group">
<span class="input-group-addon" id="disp_exec">{{ $service->executable }}</span>
<input type="text" name="startup" class="form-control" value="{{ old('startup', $service->startup) }}" />
</div>
<p class="text-muted"><small>This is the default startup that will be used for all servers created using this service. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Save Changes" />
</div>
</div>
</form>
</div>
<form action="{{ route('admin.services.service', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger">
Deleting a service is an irreversible action. A service can <em>only</em> be deleted if no servers are associated with it.
</div>
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete Service" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('input[name="executable"]').on('keyup', function() {
$("#disp_exec").html(escape($(this).val()));
});
});
</script>
@endsection

View file

@ -42,7 +42,7 @@
<tbody>
@foreach ($users as $user)
<tr>
<td><a href="/admin/accounts/view/{{ $user->id }}"><code>{{ $user->email }}</code></a> @if($user->root_admin === 1)<span class="badge">Administrator</span>@endif</td>
<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>
@ -55,7 +55,7 @@
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/accounts']").addClass('active');
$('#sidebar_links').find("a[href='/admin/users']").addClass('active');
});
</script>
@endsection

View file

@ -28,7 +28,7 @@
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Controls</a></li>
<li><a href="/admin/accounts">Accounts</a></li>
<li><a href="/admin/users">Accounts</a></li>
<li class="active">Add New Account</li>
</ul>
<h3>Create New Account</h3><hr />
@ -88,7 +88,7 @@ $(document).ready(function(){
});
});
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/accounts/new']").addClass('active');
$('#sidebar_links').find("a[href='/admin/users/new']").addClass('active');
});
</script>
@endsection

View file

@ -0,0 +1,160 @@
{{-- Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com> --}}
{{-- Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com> --}}
{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}}
{{-- of this software and associated documentation files (the "Software"), to deal --}}
{{-- in the Software without restriction, including without limitation the rights --}}
{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}}
{{-- copies of the Software, and to permit persons to whom the Software is --}}
{{-- furnished to do so, subject to the following conditions: --}}
{{-- The above copyright notice and this permission notice shall be included in all --}}
{{-- copies or substantial portions of the Software. --}}
{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}}
{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}}
{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}}
{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}}
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
@extends('layouts.admin')
@section('title')
Viewing User
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Controls</a></li>
<li><a href="/admin/users">Accounts</a></li>
<li class="active">{{ $user->email }}</li>
</ul>
<h3>Viewing User: {{ $user->email }}</h3><hr />
<div class="row">
<form action="{{ route('admin.users.view', $user->id) }}" method="post">
<div class="col-md-6">
<fieldset>
<div class="form-group">
<label for="email" class="control-label">{{ trans('strings.email') }}</label>
<div>
<input type="text" name="email" value="{{ $user->email }}" class="form-control">
</div>
</div>
<div class="form-group">
<label for="registered" class="control-label">{{ trans('strings.registered') }}</label>
<div>
<input type="text" value="{{ $user->created_at }}" readonly="readonly" class="form-control">
</div>
</div>
<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 class="form-group">
{!! csrf_field() !!}
<input type="submit" value="{{ trans('base.account.update_user') }}" class="btn btn-primary btn-sm">
</div>
</fieldset>
</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>
<div>
<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>
</form>
</div>
<div class="row">
<div class="col-md-12">
<h3>Associated Servers</h3><hr>
@if($servers)
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th style="width:2%;"></th>
<th>Server Name</th>
<th>Node</th>
<th>Connection</th>
<th style="width:10%;"></th>
</tr>
</thead>
<tbody>
@foreach($servers as $server)
<tr>
<td><a href="/server/{{ $server->uuidShort }}/"><i class="fa fa-tachometer"></i></a></td>
<td><a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</a></td>
<td>{{ $server->nodeName }}</td>
<td><code>{{ $server->ip }}:{{ $server->port }}</code></td>
<td>@if($server->active)<span class="label label-success">Enabled</span>@else<span class="label label-danger">Disabled</span>@endif</td>
</td>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info">There are no servers associated with this account.</div>
@endif
<a href="/admin/servers/new?email={{ $user->email }}"><button type="button" class="btn btn-success btn-sm">{{ trans('server.index.add_new') }}</button></a>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>Delete Account</h3><hr />
<div class="alert alert-danger"><strong>Warning!</strong> There most be no servers associated with this account in order for it to be deleted.</div>
<form action="{{ route('admin.users.view', $user->id) }}" method="POST">
{!! method_field('DELETE') !!}
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete User" />
</form>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$("#sidebar_links").find("a[href='/admin/users']").addClass('active');
$('#delete').click(function() {
if(confirm('{{ trans('base.confirm') }}')) {
$('#delete').load($(this).attr('href'));
}
});
$("#gen_pass_bttn").click(function (event) {
event.preventDefault();
$.ajax({
type: "GET",
url: "/password-gen/12",
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
success: function(data) {
$("#gen_pass").html('<strong>Generated Password:</strong> ' + data).slideDown();
$('input[name="password"], input[name="password_confirmation"]').val(data);
return false;
}
});
return false;
});
});
</script>
@endsection

View file

@ -19,7 +19,7 @@
{{-- SOFTWARE. --}}
@extends('layouts.master')
@section('title', 'Account TOTP Settings')
@section('title', 'Account Security')
@section('sidebar-server')
@endsection
@ -34,7 +34,46 @@
</div>
@endforeach
@endforeach
<h3 style="margin-top:0;">{{ trans('base.account.totp_header') }} <small>@if (Auth::user()->use_totp === 1){{ trans('strings.enabled') }}@else{{ trans('strings.disabled') }}@endif</small></h3><hr />
<h3 style="margin-top:0;">Active Sessions</h3><hr />
<table class="table table-bordered table-hover" style="margin-bottom:0;">
<thead>
<tr>
<th>Session ID</th>
<th>IP Address</th>
<th>User Agent</th>
<th>Last Location</th>
<th>Last Activity</th>
<th></th>
</th>
</thead>
<tbody>
@foreach($sessions as $session)
<tr>
<?php $prev = unserialize(base64_decode($session->payload)) ?>
<td><code>{{ substr($session->id, 0, 8) }}</code></td>
<td>{{ $session->ip_address }}</td>
<td><small>{{ $session->user_agent }}</small></td>
<td>
@if(isset($prev['_previous']['url']))
{{ str_replace(env('APP_URL'), '', $prev['_previous']['url']) }}
@else
<em>unknwon</em>
@endif
</td>
<td>
@if((time() - $session->last_activity < 10))
<em>just now</em>
@else
{{ date('D, M j \a\t H:i:s', $session->last_activity) }}
@endif
</td>
<td><a href="{{ route('account.security.revoke', $session->id) }}"><i class="fa fa-trash-o"></i></a></td>
</tr>
@endforeach
</tbody>
</table>
<h3>{{ trans('base.account.totp_header') }} <small>@if (Auth::user()->use_totp === 1){{ trans('strings.enabled') }}@else{{ trans('strings.disabled') }}@endif</small></h3><hr />
@if (Auth::user()->use_totp === 1)
<div class="panel panel-default">
<div class="panel-heading">{{ trans('base.account.totp_disable') }}</div>
@ -112,7 +151,7 @@
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find('a[href=\'/account/totp\']').addClass('active');
$('#sidebar_links').find('a[href=\'/account/security\']').addClass('active');
$('#close_reload').click(function () {
location.reload();

View file

@ -65,10 +65,10 @@
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Accounts <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">users <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/accounts">Find Account</a></li>
<li><a href="/admin/accounts/new">New Account</a></li>
<li><a href="/admin/users">Find Account</a></li>
<li><a href="/admin/users/new">New Account</a></li>
</ul>
</li>
<li class="dropdown">
@ -133,8 +133,8 @@
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Account Management</strong></a>
<a href="/admin/accounts" class="list-group-item">Find Account</a>
<a href="/admin/accounts/new" class="list-group-item">New Account</a>
<a href="/admin/users" class="list-group-item">Find Account</a>
<a href="/admin/users/new" class="list-group-item">New Account</a>
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Server Management</strong></a>

View file

@ -182,7 +182,7 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ trans('pagination.sidebar.account_controls') }} <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/account">{{ trans('pagination.sidebar.account_settings') }}</a></li>
<li><a href="/account/totp">{{ trans('pagination.sidebar.account_security') }}</a></li>
<li><a href="/account/security">{{ trans('pagination.sidebar.account_security') }}</a></li>
<li><a href="/index">{{ trans('pagination.sidebar.servers') }}</a></li>
</ul>
</li>
@ -239,7 +239,7 @@
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>{{ trans('pagination.sidebar.account_controls') }}</strong></a>
<a href="/account" class="list-group-item">{{ trans('pagination.sidebar.account_settings') }}</a>
<a href="/account/totp" class="list-group-item">{{ trans('pagination.sidebar.account_security') }}</a>
<a href="/account/security" class="list-group-item">{{ trans('pagination.sidebar.account_security') }}</a>
<a href="/" class="list-group-item">{{ trans('pagination.sidebar.servers') }}</a>
</div>
@section('sidebar-server')

View file

@ -28,6 +28,7 @@
{!! Theme::css('css/metricsgraphics.css') !!}
{!! Theme::js('js/d3.min.js') !!}
{!! Theme::js('js/metricsgraphics.min.js') !!}
{!! Theme::js('js/async.min.js') !!}
@endsection
@section('content')
@ -167,6 +168,7 @@ $(window).load(function () {
target: document.getElementById('chart_memory'),
x_accessor: 'date',
y_accessor: 'memory',
animate_on_load: false,
y_rug: true,
area: false,
};
@ -200,6 +202,7 @@ $(window).load(function () {
y_accessor: 'cpu',
aggregate_rollover: true,
missing_is_hidden: true,
animate_on_load: false,
area: false,
};
@ -229,46 +232,60 @@ $(window).load(function () {
'cpu': ({{ $server->cpu }} > 0) ? parseFloat(((proc.data.cpu.total / {{ $server->cpu }}) * 100).toFixed(3).toString()) : proc.data.cpu.total
});
// Remove blank values from listing
var activeCores = [];
for (i = 0, length = proc.data.cpu.cores.length; i < length; i++) {
if (proc.data.cpu.cores[i] > 0) {
activeCores.push(proc.data.cpu.cores[i]);
}
}
var modifedActiveCores = { '0': 0 };
for (i = 0, length = activeCores.length; i < length; i++) {
if (i > 7) {
modifedActiveCores['0'] = modifedActiveCores['0'] + activeCores[i];
} else {
if (activeChartArrays.indexOf(i) < 0) {
activeChartArrays.push(i);
}
modifedActiveCores[i] = activeCores[i];
}
}
for (i = 0, length = activeChartArrays.length; i < length; i++) {
if (typeof cpuGraphData[(i + 1)] === 'undefined') {
cpuGraphData[(i + 1)] = [{
'date': curDate,
'cpu': ({{ $server->cpu }} > 0) ? parseFloat((((modifedActiveCores[i] || 0)/ {{ $server->cpu }}) * 100).toFixed(3).toString()) : modifedActiveCores[i] || null
}];
} else {
if (typeof cpuGraphData[(i + 1)][20] !== 'undefined') {
cpuGraphData[(i + 1)].shift();
}
cpuGraphData[(i + 1)].push({
'date': curDate,
'cpu': ({{ $server->cpu }} > 0) ? parseFloat((((modifedActiveCores[i] || 0)/ {{ $server->cpu }}) * 100).toFixed(3).toString()) : modifedActiveCores[i] || null
async.waterfall([
function (callback) {
// Remove blank values from listing
var activeCores = [];
async.forEachOf(proc.data.cpu.cores, function(inner, i, eachCallback) {
if (proc.data.cpu.cores[i] > 0) {
activeCores.push(proc.data.cpu.cores[i]);
}
return eachCallback();
}, function () {
return callback(null, activeCores);
});
}
}
cpuGraphSettings.data = (showOnlyTotal === true) ? cpuGraphData[0] : cpuGraphData;
MG.data_graphic(memoryGraphSettings);
MG.data_graphic(cpuGraphSettings);
},
function (active, callback) {
var modifedActiveCores = { '0': 0 };
async.forEachOf(active, function (inner, i, eachCallback) {
if (i > 7) {
modifedActiveCores['0'] = modifedActiveCores['0'] + active[i];
} else {
if (activeChartArrays.indexOf(i) < 0) activeChartArrays.push(i);
modifedActiveCores[i] = active[i];
}
return eachCallback();
}, function () {
return callback(null, modifedActiveCores);
});
},
function (modified, callback) {
async.forEachOf(activeChartArrays, function (inner, i, eachCallback) {
if (typeof cpuGraphData[(i + 1)] === 'undefined') {
cpuGraphData[(i + 1)] = [{
'date': curDate,
'cpu': ({{ $server->cpu }} > 0) ? parseFloat((((modified[i] || 0)/ {{ $server->cpu }}) * 100).toFixed(3).toString()) : modified[i] || null
}];
} else {
if (typeof cpuGraphData[(i + 1)][20] !== 'undefined') cpuGraphData[(i + 1)].shift();
cpuGraphData[(i + 1)].push({
'date': curDate,
'cpu': ({{ $server->cpu }} > 0) ? parseFloat((((modified[i] || 0)/ {{ $server->cpu }}) * 100).toFixed(3).toString()) : modified[i] || null
});
}
return eachCallback();
}, function () {
return callback();
});
},
function (callback) {
cpuGraphSettings.data = (showOnlyTotal === true) ? cpuGraphData[0] : cpuGraphData;
return callback();
},
], function () {
MG.data_graphic(memoryGraphSettings);
MG.data_graphic(cpuGraphSettings);
});
});
// Socket Recieves New Query
@ -277,7 +294,7 @@ $(window).load(function () {
$('#players_notice').hide();
$('#toggle_players').show();
}
if(data['data'].players != undefined && data['data'].players.length !== 0){
if(typeof data['data'] !== 'undefined' && typeof data['data'].players !== 'undefined' && data['data'].players.length !== 0){
$('#toggle_players').html('');
$.each(data['data'].players, function(id, d) {
$('#toggle_players').append('<code>' + d.name + '</code>,');